Compare commits

...

312 Commits

Author SHA1 Message Date
Thomas Breloff c4bdc5b856 NEWS.md 2016-06-14 00:27:22 -04:00
Thomas Breloff b5b4023056 axis link arg; change those subplot/axis args set in kwlist to apply just to its own subplot; markerstrokealpha matches markeralpha 2016-06-14 00:10:26 -04:00
Thomas Breloff 0e598cc51d resolve relative coords for inset subplots 2016-06-13 17:46:19 -04:00
Thomas Breloff 9fd09924eb _shape_keys for consistent shape ordering 2016-06-13 16:57:26 -04:00
Thomas Breloff 10d218089f working on glvisualize 2016-06-13 16:15:45 -04:00
Thomas Breloff 1ba0d198a7 rename ellipse to circle; cleanup glvisualize 2016-06-13 15:59:51 -04:00
Thomas Breloff c44132d29d merge_with_base_supported updates 2016-06-13 15:45:35 -04:00
Thomas Breloff a018a2c07a series type dependencies and cleanup; bbox anchors in construction; fix for insets 2016-06-13 14:53:28 -04:00
Thomas Breloff 266d2efde5 inset_subplots; GR log axes fix 2016-06-13 12:27:48 -04:00
Thomas Breloff 3b33b054d3 plotly hover and scattergl 2016-06-13 11:04:56 -04:00
Thomas Breloff 1140b51e91 PLOTS_DEFAULTS and removed pyplot finalizer 2016-06-13 09:42:50 -04:00
Thomas Breloff 9d25c8fcd4 added PLOTS_DEFAULTS env var; check isfinite in axis_limits 2016-06-13 09:27:49 -04:00
Thomas Breloff e8ade18d47 code and utils for plotly shapes 2016-06-13 01:45:53 -04:00
Thomas Breloff ed243f4e3e added hover attribute and desc; fixed plotly title location; already_warned check 2016-06-13 00:15:28 -04:00
Thomas Breloff e0af4b7925 pyplot log scale fix; plotly title as annotation, title_location, hover placeholder 2016-06-12 23:48:18 -04:00
Thomas Breloff 80e53adbd8 getting plotly/plotlyjs up to speed 2016-06-12 21:03:11 -04:00
Thomas Breloff 36a29357a3 only widen axis limits for identity scale; closes #326 2016-06-12 20:22:57 -04:00
Thomas Breloff 5bb9aceaac pyplot log scale threshold fix; imgcomp fix to filter out higher versions 2016-06-12 13:06:15 -04:00
Thomas Breloff 02d893b316 big cleanup: changed camelcase to underscores; removed supportedAxes; changed pyplot methods to be py_ which matches gr_/plotly_/etc convention 2016-06-12 12:34:41 -04:00
Thomas Breloff b1850e58f4 gr cleanup commented code; bump version in imgcomp 2016-06-12 11:35:24 -04:00
Thomas Breloff f1e566dddc fix scale aliases 2016-06-12 11:00:42 -04:00
Thomas Breloff 2f60da33b7 changed pyplot log scale to symlog; #326 2016-06-12 10:28:41 -04:00
Thomas Breloff b0456d155a allow nothing for colorbar; gr fixes: marker_z colors, check colorbar arg 2016-06-11 15:55:55 -04:00
Tom Breloff 5f57a48996 Merge pull request #322 from jheinen/dev
Fixed GR output problem
2016-06-11 15:54:00 -04:00
Josef Heinen bc9d137001 Fixed GR output problem 2016-06-11 11:11:57 +02:00
Thomas Breloff 483946787a big GR refactor: axes drawing and cleanup; convert_to_polar and GR polar fill; should_add_to_legend fix; rename get_mod to Base.cycle 2016-06-11 02:03:46 -04:00
Thomas Breloff b165f233a8 animation fix; gr transparency 2016-06-10 16:17:30 -04:00
Thomas Breloff 3b325b2482 pyplot figure finalizer; accept 2-len AVec for limits (#289); sleep/skip/only options in test_examples 2016-06-10 10:04:33 -04:00
Tom Breloff 2dd480a3a6 Merge pull request #313 from jheinen/dev
Improved GR output
2016-06-10 08:56:53 -04:00
Josef Heinen df33ed40d6 Improved GR output
- slightly reduced the nominal marker size
- use character height as line spacing criterion when plotting legends
- use GR surface function for filled contours
- avoid calling GR setwindow/adjust* functions with Inf arguments
- corrected bounding box for legend
2016-06-10 13:39:57 +02:00
Thomas Breloff cf4d78c87c added clims attribute and support it in pyplot 2016-06-10 01:25:34 -04:00
Thomas Breloff 8007d0dd6a smarter cleanup of dicts; layout fixes: split out update_min_padding and added minimum_perimeter logic; check for nonempty axes when linking; add subplot processing and linking when combining plots 2016-06-10 00:16:13 -04:00
Thomas Breloff d7098f77c2 pgfplots display popup 2016-06-09 17:31:06 -04:00
Thomas Breloff 514284e784 pyplot display fixes for #308; setxyz fix; generic png conversion from pdf; pgfplots pdf output 2016-06-09 17:13:16 -04:00
Thomas Breloff 2a3029edb4 add build.jl 2016-06-09 12:08:36 -04:00
Thomas Breloff 45a1ed69d4 removed plotly-latest.min.js and added build.jl which downloads it; fix for quiver_using_hack; working on pyplot display behavior 2016-06-09 11:44:05 -04:00
Thomas Breloff 7248d8d7ce several fixes for type recipes; collect/zmin fixes in pgfplots; scatter3d recipe 2016-06-09 01:48:43 -04:00
Tom Breloff fa84401866 Merge pull request #310 from diegozea/dev
Add trim kw arg to violin and density
2016-06-09 01:45:43 -04:00
Diego Javier Zea 910b94c5fd Add trim kw arg to violin and density 2016-06-09 02:38:19 -03:00
Thomas Breloff 40cbddab29 updated NEWS; export shorthands macro; new example 16 2016-06-08 23:32:03 -04:00
Thomas Breloff 853cda4b80 moved stuff to PlotDocs 2016-06-08 17:34:15 -04:00
Thomas Breloff 91ec0ad077 allow false for color; properly expand extrema for tuple fillrange; make_fillrange_from_ribbon 2016-06-08 09:51:27 -04:00
Thomas Breloff 4be2304ec1 working on arg_desc; violin fix for groups 2016-06-08 00:52:16 -04:00
Tom Breloff d0ee402939 Merge pull request #306 from diegozea/dev
Solve a bug with whisker lengths (#210)
2016-06-07 23:01:29 -04:00
Diego Javier Zea 2d095daddc Solve a bug with whisker lengths (#210) 2016-06-07 23:44:41 -03:00
Thomas Breloff cf4fcf3351 fixed boxplot grouping; cleaned up GroupBy recipe; added arg_descs 2016-06-07 21:12:59 -04:00
Thomas Breloff 42d57fb8c2 working on arg_desc 2016-06-07 18:17:27 -04:00
Thomas Breloff ba13c1528f working on arg_desc 2016-06-07 17:59:30 -04:00
Thomas Breloff 857fb3342a fakedata fix 2016-06-07 17:16:14 -04:00
Thomas Breloff 17189e3125 default_should_widen; symbol in userplot macro; cleanup in recipes 2016-06-07 16:44:15 -04:00
Thomas Breloff d0209d945d widen further; added zorder to pyplot annotations 2016-06-07 12:38:35 -04:00
Tom Breloff 1b6bfa1259 Merge pull request #305 from diegozea/dev
Add notch and outliers to boxplot
2016-06-07 12:09:55 -04:00
Thomas Breloff 4ea813c983 pyplot scatter fix 2016-06-07 12:07:16 -04:00
Diego Javier Zea 71d2af07da Add notch and outliers to boxplot 2016-06-07 13:06:13 -03:00
Thomas Breloff b36f8c701e type recipes 2016-06-07 11:59:38 -04:00
Thomas Breloff d64d3ef101 pgfplots fixes 2016-06-07 01:57:02 -04:00
Thomas Breloff 05fa4a92de fixes in plotly/plotlyjs; smooth primary false 2016-06-07 01:42:33 -04:00
Thomas Breloff 990314cefc fixes for gr markers; add cbar to example 24; turn on gr tests 2016-06-07 01:23:07 -04:00
Thomas Breloff 60d97ad9c9 added density recipe 2016-06-07 00:18:07 -04:00
Thomas Breloff 6bfa31a89f added primary kw for series counting; added sticks recipe; gr_set_font and related fixes 2016-06-06 23:43:06 -04:00
Thomas Breloff 8d8ac10429 step recipes 2016-06-06 18:19:46 -04:00
Thomas Breloff 56f398fb55 histogram2d recipe; handle smoothing generically 2016-06-06 17:55:09 -04:00
Thomas Breloff cf5aed00e2 test fix 2016-06-06 16:12:22 -04:00
Thomas Breloff de616dfdf0 update_min_padding fix; shape support in GR; GR attr fixes; image extrema and fixes; hline/vline recipes; nobigs closes #303 2016-06-06 15:34:51 -04:00
Thomas Breloff 3cf428d7ea pyplot colorbar fix; annotation fix; new contour example; travis fix 2016-06-06 14:06:55 -04:00
Thomas Breloff 29d93ba33e changed axis defaults byletter to match; cleaned up examples; pyplot extrema fixes; commented out gr tests 2016-06-06 12:34:24 -04:00
Thomas Breloff 76c84c7615 attr fixes; allow_fail for nightly travis; series_new cleanup 2016-06-06 01:36:07 -04:00
Thomas Breloff e129c29e2f postpone match logic and add color_or_nothing; update pyplot for attr changes; move some pyplot logic later 2016-06-06 01:19:25 -04:00
Thomas Breloff 52d325b446 Merge remote-tracking branch 'origin/master' into dev 2016-06-05 23:59:06 -04:00
Thomas Breloff 74efd98699 added user_attr field to Plot; fillrange fix; pyplot title fix 2016-06-05 23:58:35 -04:00
Tom Breloff 28effaea36 Merge pull request #298 from dhoegh/fix_animation
Make animation use ImageMagick.jl if it is installed.
2016-06-05 23:16:05 -04:00
Daniel Høegh 6256549f44 Make animation use ImageMagick.jl if it is installed. 2016-06-05 09:31:44 +02:00
Thomas Breloff 2adec2fa7b examples 2016-06-02 20:34:25 -05:00
Thomas Breloff 32e816b713 working on axis limit fixes 2016-06-02 20:22:58 -05:00
Thomas Breloff 6c7b113de5 working on new tests 2016-06-02 17:59:24 -05:00
Thomas Breloff 1d5cce61ac working on switch to PlotReferenceImages 2016-06-02 17:23:40 -05:00
Thomas Breloff 85f91b9075 fix for plotting function; closes #291 2016-06-01 14:45:35 -05:00
Thomas Breloff aa64a0d3ce pyplot color fix 2016-06-01 14:29:36 -05:00
Thomas Breloff e9d95a85b7 proper grid layout in macro; pyplot isx fix 2016-05-30 14:56:32 -05:00
Thomas Breloff 625c92a985 working on 0.5 changes 2016-05-28 11:17:34 -04:00
Thomas Breloff 3cc9425219 working on 0.5 changes -- String 2016-05-28 11:00:19 -04:00
Thomas Breloff 0a86ccb142 working on 0.5 changes 2016-05-28 10:42:41 -04:00
Thomas Breloff ce82e07dc9 Extrema type and link_axis; link keyword, removed old link logic 2016-05-27 22:03:56 -04:00
Thomas Breloff 048c60614c check _axis_defaults in default 2016-05-27 16:31:07 -04:00
Thomas Breloff 148fa6d151 discrete_values handling; pyplot lims/widen; cleanup 2016-05-27 16:23:46 -04:00
Thomas Breloff 088543176f recompute_lengths fix; renamed hist to histogram, hist2d to histogram2d to match methods; fix tests 2016-05-27 15:08:05 -04:00
Thomas Breloff 536746d47e switch order for Surface function constructor; closes #273 2016-05-27 14:38:23 -04:00
Thomas Breloff c15cbad396 shorthands macro; pyplot bar fill fix; userplot macro; nicer error message in convertToAnyVector 2016-05-27 14:25:14 -04:00
Thomas Breloff d3baa58c7e series ordering bug fix 2016-05-27 12:55:29 -04:00
Thomas Breloff 7c885de6c7 improved trickery with layout macro 2016-05-27 12:16:34 -04:00
Thomas Breloff 010b8a382e working on building plot from existing plots 2016-05-27 10:58:45 -04:00
Thomas Breloff 314053af54 gr hist2d fix; closes #253 2016-05-27 09:05:48 -04:00
Thomas Breloff 34a7f8a99c prepared_object; closes #267 2016-05-27 00:19:12 -04:00
Thomas Breloff 87031984d9 widen gr limits; fixes #265 2016-05-27 00:14:22 -04:00
Thomas Breloff 83687f96a5 PLOTS_USE_ATOM_PLOTPANE env var; closes #270 2016-05-26 23:56:11 -04:00
Thomas Breloff f33905ed80 fixes for images/heatmaps/surfaces 2016-05-26 23:47:09 -04:00
Thomas Breloff f91c4d586c html_output_format 2016-05-26 20:57:51 -04:00
Thomas Breloff 77ec4adcad default minpad fix; html_output_format 2016-05-26 17:04:28 -04:00
Thomas Breloff b5a8186d99 working on pgfplots; default minpad calcs; moved subplot/layout init later to allow setting in recipes 2016-05-26 16:01:46 -04:00
Thomas Breloff ed25e554e1 working on pgfplots 2016-05-26 15:02:04 -04:00
Thomas Breloff a388ec71e3 working on pgfplots 2016-05-26 14:18:57 -04:00
Thomas Breloff bde3c2e19d working on pgfplots 2016-05-26 13:47:00 -04:00
Thomas Breloff d1d8d196ba sticks recipe; fixed up unicodeplots backend 2016-05-26 11:19:47 -04:00
Thomas Breloff 7be5aebbc9 gr fixes: polar markers, empty legends, pie labels, scale and flip 2016-05-25 16:52:07 -04:00
Thomas Breloff d15fcb6b24 gr_polyline fixes 2016-05-25 16:01:18 -04:00
Thomas Breloff b3ac38a6f1 working on gr 2016-05-25 15:17:51 -04:00
Thomas Breloff dae9dad2f7 working on GR; series_list and should_add_to_legend; series recipes fix; hist and bar recipes 2016-05-25 13:06:08 -04:00
Thomas Breloff 603dc30bb1 backend callback changes; output callbacks; prepare_output; fixes for pyplot, plotly, plotlyjs; and more 2016-05-24 23:19:51 -04:00
Tom Breloff 71070da13e Merge pull request #276 from spencerlyon2/dev
Fix plotlyjs subplots
2016-05-24 20:48:25 -04:00
Spencer Lyon 8fa1d4eb9b Fix plotlyjs subplots 2016-05-24 17:21:07 -07:00
Thomas Breloff 41f91f9cfb working on GR rebuild 2016-05-24 18:01:20 -04:00
Thomas Breloff 5699f3c6bc begin GR cleanup 2016-05-24 15:22:16 -04:00
Thomas Breloff 300babd6b1 deprecated gadfly/immerse 2016-05-24 14:46:53 -04:00
Thomas Breloff c1baca181c plotly subplot layouts 2016-05-24 14:25:03 -04:00
Thomas Breloff 851fe9f72c plotlyjs fixes 2016-05-24 12:38:52 -04:00
Thomas Breloff cf635cbdd9 renamed plotargs to attr; working on plotly backend 2016-05-24 12:20:03 -04:00
Thomas Breloff abbd8635dd pyplot hist in legend fix; closes #254 2016-05-24 11:03:38 -04:00
Thomas Breloff 2c2218bcb9 layouts fixed 2016-05-24 10:34:02 -04:00
Thomas Breloff bb6c06d3f7 working on layouts 2016-05-24 08:30:46 -04:00
Thomas Breloff 90e22f179e discrete value fixes; margin args; check for fillrange in contourf; skip test 6 2016-05-23 18:15:33 -04:00
Thomas Breloff 0a00d1fbf3 suppress_warnings fix; discrete values fixes 2016-05-23 16:42:24 -04:00
Thomas Breloff 6f0b8d1d7b group fix; ohlc fix 2016-05-23 15:13:20 -04:00
Thomas Breloff ef0d99340a backend template renaming/reorg/cleanup, pyplot fixes 2016-05-23 10:29:35 -04:00
Thomas Breloff d4d388a94b pyplot colorbar labels bbox 2016-05-21 23:03:55 -04:00
Thomas Breloff 7022ce7553 fixes for push/append/set_xy 2016-05-21 10:03:47 -04:00
Thomas Breloff 4a96122067 colorbar layout fixes; process_axis_arg and related fix 2016-05-21 00:04:33 -04:00
Thomas Breloff 862ac3af8a fixes/improvements to annotations; added series_annotations keyword 2016-05-20 11:38:07 -04:00
Thomas Breloff 333c2765fe ohlc recipe and related fixes; removed ohlc-specific code from gadfly and gr 2016-05-20 10:06:15 -04:00
Thomas Breloff 89feb68b7c pyplot lims fix 2016-05-20 08:15:27 -04:00
Thomas Breloff 1b901673fc fixes for guide/dataframes; 3d/polar projections; delayed subplot init; fg_title 2016-05-19 23:45:07 -04:00
Thomas Breloff fb60c244f6 bar_width and bar_edges keywords; small fixes 2016-05-19 17:34:37 -04:00
Thomas Breloff 3cea2df459 fix get_subplot_index; fix pyplot colorbar 2016-05-19 14:00:39 -04:00
Thomas Breloff 1cf514d1e6 merged dev into tb_recipes 2016-05-19 13:03:01 -04:00
Thomas Breloff 65e18a8669 upper bound on RecipesBase in REQUIRE; added NEWS.md; closes #259 2016-05-19 12:36:34 -04:00
Thomas Breloff 3e8f325ddc reorg/cleanup; removed old layouts and subplots; created axes.jl and layouts.jl 2016-05-19 10:42:59 -04:00
Thomas Breloff 0d96c49f4a layout macro; attr rename; getindex; fixes and cleanup 2016-05-18 23:55:03 -04:00
Thomas Breloff bfc3fc1dec titles and sub-layouts 2016-05-18 15:53:00 -04:00
Thomas Breloff 327f235af9 working on subplot/axis arg processing 2016-05-18 14:08:44 -04:00
Thomas Breloff df7fc0c4df fixes; basic layout works 2016-05-17 23:37:47 -04:00
Thomas Breloff 33d9664df5 working on layouts 2016-05-17 21:24:34 -04:00
Thomas Breloff 0d237028e7 redo the bbox logic 2016-05-17 17:41:03 -04:00
Thomas Breloff 80b9115393 fixes 2016-05-17 15:55:54 -04:00
Thomas Breloff 606229a08f trying out measures 2016-05-17 15:45:05 -04:00
Thomas Breloff 2ac17afe30 fixes 2016-05-17 10:33:10 -04:00
Tom Breloff 70f23e695b Merge pull request #252 from pkofod/join
Replace string cats with join() in pgfplots (and more).
2016-05-17 09:09:04 -04:00
Thomas Breloff 3084b0c41c working on arg overhaul 2016-05-17 00:57:42 -04:00
Thomas Breloff b75db9e473 arg defaults 2016-05-16 21:56:27 -04:00
Thomas Breloff c89e9c5f73 refactored and renamed plot defaults into plot,subplot,axis defaults, plus some associated fixes 2016-05-16 20:57:50 -04:00
Thomas Breloff 61107b8577 working on layout 2016-05-16 17:12:45 -04:00
Thomas Breloff 35ff449dc3 working on layout 2016-05-16 15:56:12 -04:00
Thomas Breloff ae1f5b8b06 working on layouts 2016-05-16 14:24:45 -04:00
Thomas Breloff c5bcae1e34 working on subplots 2016-05-16 01:07:33 -04:00
Thomas Breloff e9ab6e7301 started subplots/layouts 2016-05-15 12:32:10 -04:00
Thomas Breloff a6ce2e691d changed add_series to accept Series and push to list automatically; default add_series simplifies; removed old supported.jl 2016-05-15 12:14:18 -04:00
Thomas Breloff bfe04bdd15 changed to _create_backend_figure; started subplot removal 2016-05-15 11:23:27 -04:00
Thomas Breloff 2d0f9f01d5 cleanup 2016-05-14 17:34:44 -04:00
Thomas Breloff 87a967ad9c working on Axis 2016-05-14 00:10:58 -04:00
Thomas Breloff 290b17a5f9 fixes 2016-05-13 22:53:32 -04:00
Thomas Breloff 76572b990b fix gr linetype 2016-05-13 21:00:02 -04:00
Thomas Breloff 54efb48d62 changed linetype to seriestype 2016-05-13 20:55:26 -04:00
Thomas Breloff 83bd780f2a working on Axis type 2016-05-13 16:46:15 -04:00
Thomas Breloff 2ba4ac6d20 working on Axis type 2016-05-13 14:42:11 -04:00
Thomas Breloff 98dc52f124 working on Axis type 2016-05-13 13:45:30 -04:00
Thomas Breloff fbb2f30f3f errorbars, ribbons, and marker_z 2016-05-13 11:58:29 -04:00
Thomas Breloff 8ec3d18339 boxplot, violin, quiver, and line series recipes; plus some fixes 2016-05-13 11:21:32 -04:00
Thomas Breloff 26779cbf4a series recipes 2016-05-13 08:03:28 -04:00
Thomas Breloff 4f429fb536 recipe overhaul; groups, annotations, SliceIt 2016-05-13 00:17:58 -04:00
Thomas Breloff 7e1706f700 working on core recipes 2016-05-12 21:30:52 -04:00
Thomas Breloff d491b29037 working on recipes overhaul 2016-05-12 16:35:57 -04:00
Thomas Breloff cf9b60660d working on recipes overhaul 2016-05-12 14:51:53 -04:00
Thomas Breloff a5e9ad9f19 working on recipes overhaul 2016-05-12 14:00:47 -04:00
Thomas Breloff 6049a9fa0a working on recipes overhaul 2016-05-12 12:31:47 -04:00
Patrick Kofod Mogensen 5196ce5263 Add PGFPlots support for the size kw. 2016-05-12 17:32:54 +02:00
Patrick Kofod Mogensen 9231da4610 Clean up wall of pushes in _pgfplots_get_marker, and lines starting with kwarg. 2016-05-12 17:32:54 +02:00
Patrick Kofod Mogensen 89f8030ba1 Add markerstrokewidth to pgfplots. 2016-05-12 17:32:54 +02:00
Patrick Kofod Mogensen 534f57b227 Replace string cats with join() in pgfplots. 2016-05-12 17:32:54 +02:00
Thomas Breloff 4d3ebf31d4 add series_new.jl 2016-05-12 10:28:04 -04:00
Tom Breloff d778c7922d Merge pull request #255 from jheinen/dev
Fixed some bugs in gr.jl
2016-05-12 10:12:29 -04:00
Josef Heinen 5109c6f72b Fixed some bugs in gr.jl
- Turn off interpolation for heatmaps
- Fixed bug plotting scatter after heatmap (scatter drew to the colorbar
  viewport)
- Don't draw superfluous legends
2016-05-12 15:29:43 +02:00
Thomas Breloff 5005d32afd recipes overhaul; is_installed and dataframes logic; removed requires 2016-05-11 22:12:51 -04:00
Thomas Breloff 88be655dca started recipe reorg 2016-05-11 17:56:18 -04:00
Tom Breloff fe5789d747 Merge pull request #251 from jheinen/dev
Added (native) image support
2016-05-11 10:41:39 -04:00
Josef Heinen 00201b5f9c Added (native) image support 2016-05-11 16:18:03 +02:00
Thomas Breloff 85f7acf9d5 add PyPlot.ioff to init; closes #229 2016-05-11 09:45:44 -04:00
Thomas Breloff 44d1cdc2b2 pgfplots aspect_ratio 2016-05-11 09:18:40 -04:00
Tom Breloff 90c2202f6e Merge pull request #250 from KristofferC/kc/fix_pgfplots_color
Fix bug in some linecolors in PGFPlots
2016-05-11 09:11:13 -04:00
Kristoffer Carlsson d30574de24 workaround a bug in engineering notation for colors in pgfplot 2016-05-11 15:01:17 +02:00
Tom Breloff dec3748fe5 Merge pull request #249 from KristofferC/kc/pgfplots_label
add support for legend and label in pgfplots
2016-05-11 08:51:38 -04:00
Kristoffer Carlsson e8cdff2310 add support for legend and label in pgfplots
also adds some extra kwargs for legend positions
2016-05-11 13:59:32 +02:00
Thomas Breloff a0ca9d675f add_arrows function; plotly cleanup and arrows attempt 2016-05-10 17:22:47 -04:00
Thomas Breloff baf410c712 remove pyplot dash_capstyle option 2016-05-10 14:34:28 -04:00
Thomas Breloff a81af2b449 arrow conveniences 2016-05-10 13:57:38 -04:00
Thomas Breloff 678dde710b arrows with updated quiver recipe; only pyplot so far 2016-05-10 13:40:25 -04:00
Tom Breloff 93a35d8fdc Merge pull request #244 from jheinen/dev
Improved scaling of plots with error bars
2016-05-10 12:21:18 -04:00
Josef Heinen 0878f44b09 Improved scaling of plots with error bars 2016-05-10 18:14:08 +02:00
Thomas Breloff b89ae245ec removed heatmap assert 2016-05-09 22:44:22 -04:00
Thomas Breloff 536da01714 boxplot nan fix; add match_dimensions to pyplot supportedargs 2016-05-09 22:13:53 -04:00
Thomas Breloff a1b991aaeb image fixes and support in pyplot 2016-05-09 15:53:33 -04:00
Thomas Breloff 7bee9a5829 setTicksFromStringVector fixes; travis 2016-05-09 14:36:09 -04:00
Thomas Breloff 17a90e3b00 revamped setTicksFromStringVector; dataframes label fix 2016-05-09 14:00:07 -04:00
Tom Breloff 43ba9b1172 Merge pull request #239 from jheinen/dev
Fix axes offset bug (#221)
2016-05-09 11:28:04 -04:00
Thomas Breloff fdb01046e6 plotly zaxis, tick rotation, 3d axis fix; dataframes recipe fixes 2016-05-09 11:19:20 -04:00
Josef Heinen 09421c9189 Fix axes offset bug (#221) 2016-05-09 17:10:20 +02:00
Thomas Breloff c563204b0a added RecipesBase dep 2016-05-09 09:01:05 -04:00
Thomas Breloff 33b3ee89f3 replaced dataframes with recipes 2016-05-09 08:31:50 -04:00
Tom Breloff 008b61c9f6 Merge pull request #236 from jheinen/dev
Fixed colormap problem
2016-05-09 06:12:55 -04:00
Josef Heinen 7546db4d42 Fixed colormap problem 2016-05-09 11:44:37 +02:00
Thomas Breloff 925243969c attempted colormap fix for gr 2016-05-08 11:02:20 -04:00
Tom Breloff 7bce4a963e Merge pull request #233 from jheinen/dev
gr: support for colormaps
2016-05-08 10:30:40 -04:00
Josef Heinen 42d09a6d80 Working on colormaps 2016-05-08 14:06:11 +02:00
Josef Heinen b6334176e4 Added support for :nbins keyword 2016-05-07 13:24:11 +02:00
Josef Heinen 42a175bd53 Added support for :zlims keyword 2016-05-07 12:45:09 +02:00
Josef Heinen b4fb9af8e4 Fixed a problem with Julia v0.3 2016-05-07 07:47:13 +02:00
Thomas Breloff ed6c9fe432 add is_key_supported definition 2016-05-06 15:17:56 -04:00
Thomas Breloff 7e04aa438f removed RecipesBase from REQUIRE 2016-05-06 15:09:53 -04:00
Thomas Breloff f1679f82c1 remove recipe macro; add dependency on RecipesBase 2016-05-06 14:55:42 -04:00
Thomas Breloff 1808b81d59 recipe fix for empty args in subplot 2016-05-06 11:51:23 -04:00
Thomas Breloff cd25c7acb2 markershape fix; added require option for recipes 2016-05-06 11:39:17 -04:00
Thomas Breloff cf03779c5b handle user keywords properly for recipes 2016-05-06 10:50:54 -04:00
Thomas Breloff d3439f0d7e recipe macro handle curly 2016-05-05 16:58:04 -04:00
Thomas Breloff 2df7f84939 recipe macro 2016-05-05 16:18:56 -04:00
Thomas Breloff 74c76c48d5 working on plotrecipe 2016-05-05 12:22:31 -04:00
Thomas Breloff cdc0d22b01 changed recipe to plotrecipe and improved it 2016-05-05 11:51:50 -04:00
Thomas Breloff 5a520aadc0 removed some debugging 2016-05-05 10:35:04 -04:00
Thomas Breloff ec87489dd4 working on image type; working on ijulia display_dict 2016-05-05 00:13:45 -04:00
Thomas Breloff e30affdfc2 made center calc centroid 2016-05-04 21:36:15 -04:00
Thomas Breloff 66bd2559b9 recipe macro; markershape fix; moved/fixed themes 2016-05-04 16:09:09 -04:00
Thomas Breloff c8d6b1fab0 linewidth fix; kw macro 2016-05-04 14:24:15 -04:00
Thomas Breloff 548c59719b linewidth default 2016-05-04 13:38:08 -04:00
Thomas Breloff 59c1e24371 default_kw and force_kw macros for recipes 2016-05-04 13:24:44 -04:00
Thomas Breloff 3c09921cbd x/y/z ticklabel rotation; new supported graphs using heatmap 2016-05-03 17:14:52 -04:00
Thomas Breloff 00f3d450f9 fix for integer arg in process_inputs 2016-05-03 14:43:34 -04:00
Thomas Breloff e676387c16 shapes change verts to x/y; translate/scale/rotate/center; x/y scale alias fix 2016-05-02 16:37:10 -04:00
Thomas Breloff 2e2e8a2230 move plotargs merge and handlePlotColors before series 2016-05-02 12:16:41 -04:00
Thomas Breloff 8c8ede361e removed plotly tests 2016-04-29 17:00:56 -04:00
Thomas Breloff 672bf05856 travis 2016-04-29 14:03:50 -04:00
Thomas Breloff 67ec31c75a travis 2016-04-29 13:52:22 -04:00
Thomas Breloff 59d13a7810 travis; readme 2016-04-29 13:41:28 -04:00
Thomas Breloff 010a25f4cb travis; readme 2016-04-29 13:29:58 -04:00
Thomas Breloff a0ac7b4a1e travis; readme 2016-04-29 13:02:11 -04:00
Thomas Breloff ab7eb89466 travis; gitter badge 2016-04-29 12:44:05 -04:00
Thomas Breloff 649d362b85 travis install_wkhtmltoimage.sh 2016-04-29 12:20:18 -04:00
Thomas Breloff cb80860d6f travis install_wkhtmltoimage.sh 2016-04-29 12:15:42 -04:00
Thomas Breloff a8ef9e66fc travis install_wkhtmltoimage.sh 2016-04-29 12:05:38 -04:00
Thomas Breloff d27212e0b7 travis 2016-04-29 11:30:47 -04:00
Thomas Breloff 65650a7ae5 travis 2016-04-29 11:26:53 -04:00
Thomas Breloff 6ab5b8eb68 travis 2016-04-29 10:59:10 -04:00
Thomas Breloff c618e0577a travis and tests 2016-04-29 10:49:11 -04:00
Thomas Breloff 0e84c91451 split out supported.jl, added tests for more backends; closes #206 2016-04-29 10:13:41 -04:00
Thomas Breloff 005ce11313 InputWrapper and pyplot surface zcolor 2016-04-28 22:03:52 -04:00
Tom Breloff db118d4f5a Merge pull request #208 from pkofod/newpgf
Further changes to PGFPlots backend.
2016-04-28 15:13:58 -04:00
Patrick Kofod Mogensen c92b30a741 Further changes to PGFPlots backend. 2016-04-28 20:52:56 +02:00
Thomas Breloff d67aec6fec gr fixes and tests; fillrange convertToAnyVector 2016-04-28 12:51:08 -04:00
Tom Breloff ce58f25488 Merge pull request #209 from jheinen/dev
GR - Added polar plots
2016-04-28 11:09:56 -04:00
Josef Heinen e8d77a3c61 Added polar plots 2016-04-28 16:25:32 +02:00
Thomas Breloff d6d7fc1edd gr fix zcolor to marker_z 2016-04-27 12:10:33 -04:00
Thomas Breloff 7ce7f8bfce gr tests 2016-04-27 09:20:19 -04:00
Tom Breloff f0f46e5272 Merge pull request #166 from pkofod/pkm/pgfplots
PGFPlots.jl support
2016-04-27 09:17:45 -04:00
Patrick Kofod Mogensen f9473cc8bc Initial functionality added to PGFPlots backend. 2016-04-27 10:05:48 +02:00
Thomas Breloff 5c7d31157b spy fixes; closes #196 2016-04-26 17:02:16 -04:00
Thomas Breloff 982a512ebd pyplot support for pie charts 2016-04-26 16:01:50 -04:00
Thomas Breloff 5dfd3d2f5d added aspect_ratio keyword and implemented in pyplot 2016-04-26 15:35:26 -04:00
Thomas Breloff 8d6974f67d contours keyword for surface and wireframe plots 2016-04-26 15:09:18 -04:00
Thomas Breloff 8b569e3cd8 contour3d 2016-04-26 14:35:51 -04:00
Thomas Breloff b9d20142a2 pyplot trisurface support 2016-04-26 14:20:07 -04:00
Thomas Breloff 6c9e818abd discrete colorbar for pyplot 2016-04-26 13:19:22 -04:00
Thomas Breloff d2a287d43c gr tests linux_only 2016-04-26 11:59:08 -04:00
Thomas Breloff d6ce2e364f fixes and cleanup: colorbars, fills, polar 2016-04-26 11:18:48 -04:00
Thomas Breloff 36ca485a74 added fg_guide coloring; allow markers for bar/sticks and sticks fixes; legend text color 2016-04-25 16:57:16 -04:00
Thomas Breloff b3230c5671 pyplot shapes 2016-04-25 15:14:54 -04:00
Thomas Breloff ebf490838c pyplot surface, wireframe, heatmap; sorting fixes; default linewidth is :auto 2016-04-25 15:01:24 -04:00
Thomas Breloff 7921cf18b6 pyplot contours 2016-04-25 12:47:35 -04:00
Thomas Breloff 0e0450ced5 gadfly color fixes; pyplot fill_between fix; pyplot hline/vline 2016-04-25 12:24:03 -04:00
Thomas Breloff 096064ac09 subplot layout fix; closes #202 2016-04-25 12:00:43 -04:00
Thomas Breloff 77c2d7d846 subcategories for background/foreground colors, implemented in pyplot; add_theme/set_theme and ggplot2; add_aliases function; nbins renamed bins 2016-04-25 11:37:14 -04:00
Thomas Breloff 40a121198d added weights kw; pyplot hist2d and hexbin 2016-04-24 23:33:51 -04:00
Thomas Breloff 44079a7bf7 added normalize kw, and added to pyplot 2016-04-24 22:47:32 -04:00
Thomas Breloff 50fa013801 working on pyplot refactor; barh added 2016-04-23 09:32:57 -04:00
Thomas Breloff d4ccf0ec62 PLOTS_DEFAULT_BACKEND env variable; closes #200 2016-04-23 09:04:02 -04:00
Thomas Breloff 04842974e7 working on pyplot refactor 2016-04-22 16:46:02 -04:00
Thomas Breloff 6161b08f82 working on pyplot refactor; path/scatter in 2d/3d done 2016-04-22 12:53:40 -04:00
Thomas Breloff b9818a4480 working on seriescolor and pyplot reorgs 2016-04-22 10:44:12 -04:00
Thomas Breloff e2ae22421b working on seriescolor and pyplot reorgs 2016-04-22 10:09:55 -04:00
Thomas Breloff b64677e14a changed kw splatting to passing in KW: _add_series and _create_plot; removed plotly tests 2016-04-22 08:56:54 -04:00
Thomas Breloff c910445634 quiet flag for wkhtmltoimage 2016-04-21 15:10:25 -04:00
Thomas Breloff c4683a754e plotly png generation using wkhtmltoimage 2016-04-20 17:34:55 -04:00
Thomas Breloff 1da8fdb588 plotlyjs travis 2016-04-20 15:35:23 -04:00
Thomas Breloff 4bf8e342dc plotlyjs travis; plotly/plotlyjs polar 2016-04-20 14:44:27 -04:00
Thomas Breloff 18fc50fcbf plotlyjs travis 2016-04-20 12:33:32 -04:00
Thomas Breloff 2fb10b7076 plotlyjs travis 2016-04-20 11:59:17 -04:00
Thomas Breloff 967291c2ef export KW; plotlyjs travis 2016-04-20 11:27:02 -04:00
Thomas Breloff 392ae871d5 plotlyjs travis 2016-04-20 10:57:07 -04:00
Thomas Breloff 27b3070480 Merge remote-tracking branch 'origin/master' into dev 2016-04-20 10:20:55 -04:00
Tom Breloff 325f59e03d Merge pull request #190 from mfalt/master
Better handling of xlims in Gadfly
2016-04-20 10:17:29 -04:00
Thomas Breloff 217ffb36e6 plotlyjs tests and fixes 2016-04-20 10:10:07 -04:00
Mattias Fält 4afad93ff6 Missing changes 2016-04-20 15:51:33 +02:00
Thomas Breloff e8858cb1b7 removed sort in ticks 2016-04-20 09:12:36 -04:00
Mattias Fält 52de31c9a6 Proper implementation of gadfly min/max in Cartesian 2016-04-20 14:37:34 +02:00
Mattias Fält 60660a1f1a Fixed Gadfly axis scale inversion 2016-04-20 10:48:13 +02:00
Thomas Breloff a2ea9db90e added bar_position; fixed orientation handling; pyplot cleanup 2016-04-18 15:12:13 -04:00
Thomas Breloff 236a7dadc6 added matplotlib colormaps; default changed to inferno; pyplot subplot fix 2016-04-13 13:49:03 -04:00
Thomas Breloff 82d2cc943d working on get/set xy/xyz indexing overhaul 2016-04-11 18:19:06 -04:00
Thomas Breloff d70f462899 z axis args; pyplot z axis; major pyplot cleanup; other cleanup/fixes 2016-04-11 16:16:47 -04:00
Thomas Breloff 0a636b5899 polar coords for pyplot 2016-04-11 06:46:28 -04:00
Thomas Breloff e0b2749188 added support for shape linetype in pyplot 2016-04-08 17:10:15 -04:00
Thomas Breloff 2fc973245d added quiver plot/arg; fixed gadfly shapes to allow vector of shapes 2016-04-08 16:05:20 -04:00
Thomas Breloff 9bfcb64542 asymmetric errorbars 2016-04-08 12:06:12 -04:00
Thomas Breloff 61e34b7ef8 ribbons; changed errorbar to yerror and added xerror 2016-04-08 11:52:02 -04:00
Thomas Breloff 3c3078875a errorbars 2016-04-08 10:35:17 -04:00
Thomas Breloff 5293e2c13d sort string ticks 2016-04-07 17:27:12 -04:00
Thomas Breloff fdf3b3f581 added violin plot 2016-04-07 17:05:31 -04:00
Thomas Breloff f3f29fb54f apply_series_recipe framework and boxplot; fix Gadfly scales for tick labels 2016-04-07 15:56:09 -04:00
49 changed files with 10871 additions and 5783 deletions
+1
View File
@@ -4,3 +4,4 @@
.DS_Store
examples/.ipynb_checkpoints/*
examples/meetup/.ipynb_checkpoints/*
deps/plotly-latest.min.js
+47 -10
View File
@@ -4,20 +4,57 @@ os:
- linux
- osx
julia:
- 0.4
#- nightly
- release
- nightly
matrix:
allow_failures:
- julia: 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: 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")'
# - julia -e 'Pkg.clone("https://github.com/tbreloff/Images.jl.git"); Pkg.checkout("Images","tom_imgcompare");'
# - julia -e 'Pkg.clone("Images"); Pkg.build("Images")'
- julia test/travis_commands.jl
# - julia -e 'Pkg.clone("ImageMagick"); Pkg.build("ImageMagick")'
- julia -e 'Pkg.clone("https://github.com/tbreloff/VisualRegressionTests.jl.git");'
- julia -e 'Pkg.clone("https://github.com/tbreloff/ExamplePlots.jl.git");'
# - julia -e 'Pkg.add("Cairo"); Pkg.build("Cairo")'
- julia -e 'ENV["PYTHON"] = ""; Pkg.add("PyPlot"); Pkg.build("PyPlot")'
- 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())'
# - 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())'
+195
View File
@@ -0,0 +1,195 @@
# Plots.jl NEWS
#### notes on release changes, ongoing development, and future planned work
- All new development should target 0.7!
---
## 0.7 (current master/dev)
#### 0.7.1
- inset (floating) subplots
- change: when setting subplot/axis args from user recipes, they should apply only to their own subplot
- trim for violin/boxplot
- scatter3d recipe
- removed plotly.js in favor of build.jl download
- improvements/fixes to pgfplots backend
- improvements/fixes to plotly/plotlyjs backends
- titles are annotations and properly placed with title_position
- hover attribute
- shapes (almost)
- scattergl
- minimum perimeter logic in layout calc... fixed misaligned subplots
- new clims attribute
- more options for test_examples
- GR refactor
- added transparency
- moved axis/grid logic out of series loop
- generalized 3d and polar projections
- renamed get_mod to Base.cycle
- pyplot log scale fixes
- PLOTS_DEFAULTS environment var processing
- rename :ellipse to :circle, :ellipse is now an alias
- supported args/types cleanup
- seriestype dependency methods and `@deps` macro
- bbox `h_anchor`/`v_anchor`
- new axis arg: `:link` is a list of subplots to link axes with
- cleanup/simplification of glvisualize backend
#### 0.7.0
- Check out [the summary](http://plots.readthedocs.io/en/latest/plots_v0.7/)
- Revamped and simplified internals
- [Recipes, recipes, recipes](https://github.com/JuliaPlots/RecipesBase.jl/issues/6)
- [Layouts and Subplots](https://github.com/tbreloff/Plots.jl/issues/60)
- DataFrames is loaded automatically when installed
- Overhaul to GroupBy mechanic (now offloads to a recipe)
- Replaced much of the argument processing with recipes
- Added series recipes, and began to strip down un-needed backend code. Some recipes:
- line, step, sticks, bar, histogram, histogram2d, boxplot, violin, quiver, errorbars, density, ohlc
- Added `@shorthands` and `@userplot` macros for recipe convenience
- Better handling of errorbars and ribbons
- New Axis type
- Tracks extrema and discrete values
- New `link_axes` functionality
- `linetype` has been renamed `seriestype` (the alias is reversed)
- Many fixes and huge cleanup in GR
- Brand new subplot layout mechanics:
- `@layout` macro
- AbstractLayout, Subplot, GridLayout, and everything related
- Added dependency on Measures.jl
- Computations of axis/guide sizes and precise positioning
- Refactored and compartmentalized default dictionaries for attributes
- Deprecated Gadfly and Immerse backends
- Added `series_annotations` attribute (previously that functionality was merged with `annotations`, which are not series-specific)
- Removed `axis` attribute... currently not supporting twin (right) y axes
- Check for `ENV["PLOTS_USE_ATOM_PLOTPANE"]` and default to false
- Improved backend interface to reduce redundant code. Template updated.
- Added `html_output_format`, primarily for choosing between png and svg output in IJulia.
- Partial support of Julia v0.5
- Switched testing to dump reference images to JuliaPlots/PlotReferenceImages.jl
- Moved docs-specific code to new JuliaPlots/PlotDocs.jl
- Moved example list from ExamplePlots into Plots.
- Added several examples and improved others.
- Many other smaller changes and bug fixes.
---
## Version 0.6
#### 0.6.2
- `linewidth` fixes
- `markershape` fix
- converted center calc to centroid for shapes
- new dependency on [RecipesBase](https://github.com/JuliaPlots/RecipesBase.jl)
- REQUIRE upper limit for RecipesBase: 0.0.1
- GR fixes/improvements (@jheinen)
- support `zlims`, `bins`
- allow Plots colormaps
- other bug fixes
- native image support
- PGFPlots fixes/improvements (@pkofod)
- DataFrames are handled by recipes
- Plotly: zaxis, tick rotation, 3d axis fix
- Improvements in handling discrete data
- Support for image display
- `arrow` keyword and support for adding arrows to paths
- changed quiver recipe to use arrows
- Bug fixes for boxplots, heatmaps, and more
#### 0.6.1
- `rotation` keyword
- improved supported graphs
- subplot bug fix
#### 0.6.0
- `apply_series_recipe` framework for built-in recipes
- [boxplot/violin recipes](https://github.com/tbreloff/ExamplePlots.jl/blob/master/notebooks/boxplot.ipynb)
- [errorbar/ribbon recipes](https://github.com/tbreloff/ExamplePlots.jl/blob/master/notebooks/errorbars.ipynb)
- [quiver recipe](https://github.com/tbreloff/ExamplePlots.jl/blob/master/notebooks/quiver.ipynb)
- `polar` coordinates
- better support for shapes and custom polygons (see [batman](https://github.com/tbreloff/ExamplePlots.jl/blob/master/notebooks/batman.ipynb))
- z-axis keywords
- 3D indexing overhaul: `push!`, `append!` support
- matplotlib colormap constants (`:inferno` is the new default colormap for Plots)
- `typealias KW Dict{Symbol,Any}` used in place of splatting in many places
- png generation for plotly backend using wkhtmltoimage
- `normalize` and `weights` keywords
- background/foreground subcategories for fine-tuning of looks
- `add_theme`/`set_theme` and ggplot2 theme (see [this issue](https://github.com/tbreloff/Plots.jl/issues/201))
- `PLOTS_DEFAULT_BACKEND` environment variable
- `barh` linetype
- support for non-gridded surfaces with pyplot's trisurface
- pyplot surface zcolor
- internal refactor of supported.jl
- `wrap` method to bypass input processing
- `translate`, `scale` and `rotate` methods for coordinates and shapes
- and many more minor fixes and improvements
---
## Version 0.5
#### 0.5.4
- old heatmaps have been renamed to hist2d, and true heatmaps implemented (see https://github.com/tbreloff/Plots.jl/issues/147)
- lots of reorganization and redesign of the internals
- lots of renaming to keep to conventions: AbstractPlot, AbstractBackend, etc
- initial redesign of layouts
- integration with Atom PlotPane
- arc diagram and chord diagram (thanks to @diegozea: see https://github.com/tbreloff/Plots.jl/issues/163)
- work on GR, GLVisualize, and PGFPlots backends (thanks @jheinen @dlfivefifty @pkofod)
- improvements to Plotly setup (thanks @spencerlyon2)
- overhaul to series creation logic and groupby mechanic
- replace Dict with `typealias KW Dict{Symbol,Any}` in many places, also replacing keyword arg splatting
- new `shape` linetype for plotting polygons in plot-coordinates (see https://github.com/tbreloff/ExamplePlots.jl/blob/master/notebooks/batman.ipynb)
- many other fixes
#### 0.5.3
- `@gif` macro with `every`/`when` syntax
- bezier curves and other graph drawing helpers
- added FixedSizeArrays dependency with relevant functionality
- merged lots of improvements to GR (thanks @jheinen)
- `overwrite_figure`/`reuse` arg for reusing the same figure window
- deprecated Qwt, Winston, and Bokeh backends
- improved handling of 3D inputs (call `z=rand(10,10); surface(z)` for example)
- fix IJulia display issue
- lots of progress on PlotlyJS backend
- and many other changes and fixes...
#### 0.5.2
- Added [GR.jl](https://github.com/jheinen/GR.jl) as a backend (unfinished but functional) All credit to @jheinen
- Set defaults within backend calls (i.e. `gadfly(legend=false)`)
- `abline!`; also extrema allows plotting functions without giving x (i.e. `plot(cos, 0, 10); plot!(sin)`) @pkofod @joshday
- Integration with [PlotlyJS.jl](https://github.com/spencerlyon2/PlotlyJS.jl) for using Plotly inside a Blink window @spencerlyon2
- The Plotly backend has been split into my built-in version (`plotly()`) and @spencerlyon2's backend (`plotlyjs()`)
- Revamped backend setup code for easily adding new backends
- New docs (WIP) at http://plots.readthedocs.org/
- Overhaul to `:legend` keyword (see https://github.com/tbreloff/Plots.jl/issues/135)
- New dependency on Requires, allows auto-loading of DataFrames support
- Support for plotting lists of Tuples and FixedSizeArrays
- new `@animate` macro for super simple animations (see https://github.com/tbreloff/Plots.jl/issues/111#issuecomment-181515616)
- allow Function for `:fillrange` and `zcolor` arguments (for example: `scatter(sin, 0:10, marker=15, fill=(cos,0.4), zcolor=sin)`)
- allow vectors of PlotText without x/y coords (for example: `scatter(rand(10), m=20, ann=map(text, 1:10))`)
- Lots and lots of fixes
#### 0.5.1
#### 0.5.0
- `with` function for temporary defaults
- contours
- basic 3D plotting
- preliminary support for Bokeh
- `stroke` and `brush` for more fine-tuned control over visuals
- smarter "magic" arguments: `line`, `marker`
+20 -6
View File
@@ -1,8 +1,9 @@
# Plots
[![Build Status](https://travis-ci.org/tbreloff/Plots.jl.svg?branch=master)](https://travis-ci.org/tbreloff/Plots.jl)
[![Plots](http://pkg.julialang.org/badges/Plots_0.3.svg)](http://pkg.julialang.org/?pkg=Plots&ver=0.3)
[![Plots](http://pkg.julialang.org/badges/Plots_0.4.svg)](http://pkg.julialang.org/?pkg=Plots&ver=0.4)
[![Join the chat at https://gitter.im/tbreloff/Plots.jl](https://badges.gitter.im/tbreloff/Plots.jl.svg)](https://gitter.im/tbreloff/Plots.jl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- [![Plots](http://pkg.julialang.org/badges/Plots_0.3.svg)](http://pkg.julialang.org/?pkg=Plots&ver=0.3) -->
<!-- [![Plots](http://pkg.julialang.org/badges/Plots_0.4.svg)](http://pkg.julialang.org/?pkg=Plots&ver=0.4) -->
<!-- [![Coverage Status](https://coveralls.io/repos/tbreloff/Plots.jl/badge.svg?branch=master)](https://coveralls.io/r/tbreloff/Plots.jl?branch=master) -->
<!-- [![codecov.io](http://codecov.io/github/tbreloff/Plots.jl/coverage.svg?branch=master)](http://codecov.io/github/tbreloff/Plots.jl?branch=master) -->
@@ -10,12 +11,25 @@
Plots is a plotting API and toolset. My goals with the package are:
- **Intuitive**. Start generating complex plots without reading volumes of documentation. Commands should "just work".
- **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.
- **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.
Use the preprocessing 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.
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.
View the [full documentation](http://plots.readthedocs.org).
```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
```
![waves](http://plots.readthedocs.io/en/latest/examples/img/waves.gif)
View the [full documentation](http://plots.readthedocs.io).
+2 -1
View File
@@ -1,7 +1,8 @@
julia 0.4
RecipesBase
Colors
Reexport
Compat
Requires
FixedSizeArrays
Measures
+8
View File
@@ -0,0 +1,8 @@
#TODO: download https://cdn.plot.ly/plotly-latest.min.js to deps/ if it doesn't exist
local_fn = joinpath(dirname(@__FILE__), "plotly-latest.min.js")
if !isfile(local_fn)
info("Cannot find deps/plotly-latest.min.js... downloading latest version.")
download("https://cdn.plot.ly/plotly-latest.min.js", local_fn)
end
-41
View File
File diff suppressed because one or more lines are too long
+93 -94
View File
@@ -6,66 +6,44 @@ module Plots
using Compat
using Reexport
@reexport using Colors
using Requires
# using Requires
using FixedSizeArrays
@reexport using RecipesBase
using Base.Meta
export
AbstractPlot,
Plot,
Subplot,
SubplotLayout,
AbstractLayout,
GridLayout,
RowsLayout,
FlexLayout,
grid,
EmptyLayout,
bbox,
plotarea,
@layout,
AVec,
AMat,
KW,
wrap,
set_theme,
add_theme,
plot,
plot!,
subplot,
subplot!,
current,
default,
with,
scatter,
scatter!,
bar,
bar!,
histogram,
histogram!,
histogram2d,
histogram2d!,
density,
density!,
heatmap,
heatmap!,
hexbin,
hexbin!,
sticks,
sticks!,
hline,
hline!,
vline,
vline!,
ohlc,
ohlc!,
@userplot,
@shorthands,
pie,
pie!,
contour,
contour!,
surface,
surface!,
wireframe,
wireframe!,
path3d,
path3d!,
plot3d,
plot3d!,
scatter3d,
scatter3d!,
abline!,
title!,
xlabel!,
@@ -94,10 +72,12 @@ export
Shape,
text,
font,
Axis,
stroke,
brush,
Surface,
OHLC,
arrow,
colorscheme,
ColorScheme,
@@ -111,12 +91,11 @@ export
debugplots,
supportedArgs,
supportedAxes,
supportedTypes,
supportedStyles,
supportedMarkers,
subplotSupported,
supported_args,
supported_types,
supported_styles,
supported_markers,
is_subplot_supported,
Animation,
frame,
@@ -124,14 +103,30 @@ export
@animate,
@gif,
# recipes
PlotRecipe,
# EllipseRecipe,
spy,
arcdiagram,
chorddiagram
# corrplot
chorddiagram,
test_examples,
translate,
translate!,
rotate,
rotate!,
center,
P2,
P3,
BezierCurve,
curve_points,
directed_curve
# ---------------------------------------------------------
import Measures
import Measures: Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height, w, h
typealias BBox Measures.Absolute2DBox
export BBox, BoundingBox, mm, cm, inch, pt, px, pct, w, h
# ---------------------------------------------------------
@@ -139,55 +134,60 @@ include("types.jl")
include("utils.jl")
include("colors.jl")
include("components.jl")
include("axes.jl")
include("backends.jl")
include("args.jl")
include("themes.jl")
include("plot.jl")
include("series_args.jl")
include("subplot.jl")
include("series_new.jl")
include("layouts.jl")
include("subplots.jl")
include("recipes.jl")
include("animation.jl")
include("output.jl")
include("examples.jl")
include("arg_desc.jl")
# ---------------------------------------------------------
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)
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)
pie!(args...; kw...) = plot!(args...; kw..., linetype = :pie)
contour(args...; kw...) = plot(args...; kw..., linetype = :contour)
contour!(args...; kw...) = plot!(args...; kw..., linetype = :contour)
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)
# define and export shorthand plotting method definitions
macro shorthands(funcname::Symbol)
funcname2 = Symbol(funcname, "!")
esc(quote
export $funcname, $funcname2
$funcname(args...; kw...) = plot(args...; kw..., seriestype = $(quot(funcname)))
$funcname2(args...; kw...) = plot!(args...; kw..., seriestype = $(quot(funcname)))
end)
end
@shorthands scatter
@shorthands bar
@shorthands barh
@shorthands histogram
@shorthands histogram2d
@shorthands density
@shorthands heatmap
@shorthands hexbin
@shorthands sticks
@shorthands hline
@shorthands vline
@shorthands ohlc
@shorthands contour
@shorthands contour3d
@shorthands surface
@shorthands wireframe
@shorthands path3d
@shorthands scatter3d
@shorthands boxplot
@shorthands violin
@shorthands quiver
pie(args...; kw...) = plot(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
pie!(args...; kw...) = plot!(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
plot3d(args...; kw...) = plot(args...; kw..., seriestype = :path3d)
plot3d!(args...; kw...) = plot!(args...; kw..., seriestype = :path3d)
title!(s::AbstractString; kw...) = plot!(; title = s, kw...)
@@ -241,15 +241,14 @@ yaxis!(plt::Plot, args...; kw...) = plot!(pl
const CURRENT_BACKEND = CurrentBackend(:none)
function __init__()
setup_ijulia()
setup_atom()
# 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()
if haskey(ENV, "PLOTS_DEFAULTS")
for (k,v) in eval(parse(ENV["PLOTS_DEFAULTS"]))
default(k, v)
end
end
end
# ---------------------------------------------------------
+15 -8
View File
@@ -1,12 +1,12 @@
immutable Animation
dir::ASCIIString
frames::Vector{ASCIIString}
dir::Compat.ASCIIString
frames::Vector{Compat.ASCIIString}
end
function Animation()
tmpdir = convert(ASCIIString, mktempdir())
Animation(tmpdir, ASCIIString[])
tmpdir = convert(Compat.ASCIIString, mktempdir())
Animation(tmpdir, Compat.ASCIIString[])
end
function frame{P<:AbstractPlot}(anim::Animation, plt::P=current())
@@ -21,20 +21,27 @@ end
"Wraps the location of an animated gif so that it can be displayed"
immutable AnimatedGif
filename::ASCIIString
filename::Compat.ASCIIString
end
function gif(anim::Animation, fn::@compat(AbstractString) = "tmp.gif"; fps::Integer = 20)
function gif(anim::Animation, fn = "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`)
file = joinpath(Pkg.dir("ImageMagick"), "deps","deps.jl")
if isfile(file) && !haskey(ENV, "MAGICK_CONFIGURE_PATH")
include(file)
end
prefix = get(ENV, "MAGICK_CONFIGURE_PATH", "")
run(`$(joinpath(prefix, "convert")) -delay $speed -loop 0 $(joinpath(anim.dir, "*.png")) -alpha off $fn`)
catch err
warn("Tried to create gif using convert (ImageMagick), but got error: $err\nWill try ffmpeg, but it's lower quality...)")
warn("""Tried to create gif using convert (ImageMagick), but got error: $err
ImageMagick can be installed by executing `Pkg.add("ImageMagick")`
Will try ffmpeg, but it's lower quality...)""")
# low quality
run(`ffmpeg -v 0 -framerate $fps -i $(anim.dir)/%06d.png -y $fn`)
+106
View File
@@ -0,0 +1,106 @@
const _arg_desc = KW(
# series args
:label => "String type. The label for a series, which appears in a legend. If empty, no legend entry is added.",
:seriescolor => "Color Type. The base color for this series. `:auto` (the default) will select a color from the subplot's `color_palette`, based on the order it was added to the subplot",
:seriesalpha => "Number in [0,1]. The alpha/opacity override for the series. `nothing` (the default) means it will take the alpha value of the color.",
:seriestype => "Symbol. This is the identifier of the type of visualization for this series. Choose from $(_allTypes) or any series recipes which are defined.",
:linestyle => "Symbol. Style of the line (for path and bar stroke). Choose from $(_allStyles)",
:linewidth => "Number. Width of the line (in pixels)",
:linecolor => "Color Type. Color of the line (for path and bar stroke). `:match` will take the value from `:seriescolor`, (though histogram/bar types use `:black` as a default).",
:linealpha => "Number in [0,1]. The alpha/opacity override for the line. `nothing` (the default) means it will take the alpha value of linecolor.",
:fillrange => "Number or AbstractVector. Fills area from this to y for line-types, sets the base for bar/stick types, and similar for other types.",
:fillcolor => "Color Type. Color of the filled area of path or bar types. `:match` will take the value from `:seriescolor`.",
:fillalpha => "Number in [0,1]. The alpha/opacity override for the fill area. `nothing` (the default) means it will take the alpha value of fillcolor.",
:markershape => "Symbol, Shape, or AbstractVector. Choose from $(_allMarkers).",
:markercolor => "Color Type. Color of the interior of the marker or shape. `:match` will take the value from `:seriescolor`.",
:markeralpha => "Number in [0,1]. The alpha/opacity override for the marker interior. `nothing` (the default) means it will take the alpha value of markercolor.",
:markersize => "Number or AbstractVector. Size (radius pixels) of the markers.",
:markerstrokestyle => "Symbol. Style of the marker stroke (border). Choose from $(_allStyles)",
:markerstrokewidth => "Number. Width of the marker stroke (border. in pixels)",
:markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:seriescolor`.",
:markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.",
:bins => "Integer, NTuple{2,Integer}, AbstractVector. For histogram-types, defines the number of bins, or the edges, of the histogram.",
:smooth => "Bool. Add a regression line?",
:group => "AbstractVector. Data is split into a separate series, one for each unique value in `group`.",
:x => "Various. Input data. First Dimension",
:y => "Various. Input data. Second Dimension",
:z => "Various. Input data. Third Dimension. May be wrapped by a `Surface` for surface and heatmap types.",
:marker_z => "AbstractVector. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.",
:levels => "Integer, NTuple{2,Integer}. Number of levels (or x-levels/y-levels) for a contour type.",
:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).",
:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)",
:bar_width => "nothing or Number. Width of bars in data coordinates. When nothing, chooses based on x (or y when `orientation = :h`).",
:bar_edges => "Bool. Align bars to edges (true), or centers (the default)?",
:xerror => "AbstractVector or 2-Tuple of Vectors. x (horizontal) error relative to x-value. If 2-tuple of vectors, the first vector corresponds to the left error (and the second to the right)",
:yerror => "AbstractVector or 2-Tuple of Vectors. y (vertical) error relative to y-value. If 2-tuple of vectors, the first vector corresponds to the bottom error (and the second to the top)",
:ribbon => "Number or AbstractVector. Creates a fillrange around the data points.",
:quiver => "AbstractVector or 2-Tuple of vectors. The directional vectors U,V which specify velocity/gradient vectors for a quiver plot.",
:arrow => "nothing (no arrows), Bool (if true, default arrows), Arrow object, or arg(s) that could be style or head length/widths. Defines arrowheads that should be displayed at the end of path line segments (just before a NaN and the last non-NaN point). Used in quiverplot, streamplot, or similar.",
:normalize => "Bool. Should normalize histogram types? Trying for area == 1.",
:weights => "AbstractVector. Used in histogram types for weighted counts.",
:contours => "Bool. Add contours to the side-grids of 3D plots? Used in surface/wireframe.",
:match_dimensions => "Bool. For heatmap types... should the first dimension of a matrix (rows) correspond to the first dimension of the plot (x-axis)? The default is false, which matches the behavior of Matplotlib, Plotly, and others. Note: when passing a function for z, the function should still map `(x,y) -> z`.",
:subplot => "Integer (subplot index) or Subplot object. The subplot that this series belongs to.",
:series_annotations => "AbstractVector of String or PlotText. These are annotations which are mapped to data points/positions.",
:primary => "Bool. Does this count as a 'real series'? For example, you could have a path (primary), and a scatter (secondary) as 2 separate series, maybe with different data (see sticks recipe for an example). The secondary series will get the same color, etc as the primary.",
:hover => "nothing or vector of strings. Text to display when hovering over each data point.",
# plot args
:plot_title => "String. Title for the whole plot (not the subplots) (Note: Not currently implemented)",
:background_color => "Color Type. Base color for all backgrounds.",
:background_color_outside => "Color Type or `:match` (matches `:background_color`). Color outside the plot area(s)",
:foreground_color => "Color Type. Base color for all foregrounds.",
:size => "NTuple{2,Int}. (width_px, height_px) of the whole Plot",
:pos => "NTuple{2,Int}. (left_px, top_px) position of the GUI window (note: currently unimplemented)",
:window_title => "String. Title of the window.",
:show => "Bool. Should this command open/refresh a GUI/display? This allows displaying in scripts or functions without explicitly calling `display`",
:layout => "Integer (number of subplots), NTuple{2,Integer} (grid dimensions), AbstractLayout (for example `grid(2,2)`), or the return from the `@layout` macro. This builds the layout of subplots.",
:link => "Symbol. How/whether to link axis limits between subplots. Values: `:none`, `:x` (x axes are linked by columns), `:y` (y axes are linked by rows), `:both` (x and y are linked), `:all` (every subplot is linked together regardless of layout position).",
:overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).",
:html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.",
:inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots",
# subplot args
:title => "String. Subplot title.",
:title_location => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`",
:titlefont => "Font. Font of subplot title.",
:background_color_subplot => "Color Type or `:match` (matches `:background_color`). Base background color of the subplot.",
:background_color_legend => "Color Type or `:match` (matches `:background_color_subplot`). Background color of the legend.",
:background_color_inside => "Color Type or `:match` (matches `:background_color_subplot`). Background color inside the plot area (under the grid).",
:foreground_color_subplot => "Color Type or `:match` (matches `:foreground_color`). Base foreground color of the subplot.",
:foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.",
:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.",
:foreground_color_title => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of subplot title.",
:color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.",
:legend => "Bool (show the legend?) or Symbol (legend position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` (note: only some may be supported in each backend)",
:colorbar => "Bool (show the colorbar?) or Symbol (colorbar position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:legend` (matches legend value) (note: only some may be supported in each backend)",
:clims => "`:auto` or NTuple{2,Number}. Fixes the limits of the colorbar.",
:legendfont => "Font. Font of legend items.",
:grid => "Bool. Show the grid lines?",
:annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String or PlotText (created with `text(args...)`) Add one-off text annotations at the x,y coordinates.",
:projection => "Symbol or String. '3d' or 'polar'",
:aspect_ratio => "Symbol (:equal) or Number (width to height ratio of plot area).",
:margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.",
:left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.",
:top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.",
:right_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the right of the subplot.",
:bottom_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the bottom of the subplot.",
:subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.",
# axis args
:guide => "String. Axis guide (label).",
:lims => "NTuple{2,Number}. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example).",
:ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`",
:scale => "Symbol. Scale of the axis: `:none`, `:ln`, `:log2`, `:log10`",
:rotation => "Number. Degrees rotation of tick labels.",
:flip => "Bool. Should we flip (reverse) the axis?",
:tickfont => "Font. Font of axis tick labels.",
:guidefont => "Font. Font of axis guide (label).",
:foreground_color_axis => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis ticks.",
:foreground_color_border => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of plot area border (spines).",
:foreground_color_text => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of tick labels.",
:foreground_color_guide => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis guides (axis labels).",
)
+775 -561
View File
File diff suppressed because it is too large Load Diff
+332
View File
@@ -0,0 +1,332 @@
# xaxis(args...; kw...) = Axis(:x, args...; kw...)
# yaxis(args...; kw...) = Axis(:y, args...; kw...)
# zaxis(args...; kw...) = Axis(:z, args...; kw...)
# -------------------------------------------------------------------------
function Axis(sp::Subplot, letter::Symbol, args...; kw...)
# init with values from _plot_defaults
d = KW(
:letter => letter,
# :extrema => (Inf, -Inf),
:extrema => Extrema(),
:discrete_map => Dict(), # map discrete values to discrete indices
:continuous_values => zeros(0),
:use_minor => false,
:show => true, # show or hide the axis? (useful for linked subplots)
)
# get defaults from letter version, unless match
for (k,v) in _axis_defaults
lk = Symbol(letter, k)
lv = _axis_defaults_byletter[lk]
d[k] = (lv == :match ? v : lv)
end
# merge!(d, _axis_defaults)
d[:discrete_values] = []
# update the defaults
update!(Axis(sp, d), args...; kw...)
end
function get_axis(sp::Subplot, letter::Symbol)
axissym = Symbol(letter, :axis)
if haskey(sp.attr, axissym)
sp.attr[axissym]
else
sp.attr[axissym] = Axis(sp, letter)
end
end
function process_axis_arg!(d::KW, arg, letter = "")
T = typeof(arg)
arg = get(_scaleAliases, arg, arg)
if typeof(arg) <: Font
d[Symbol(letter,:tickfont)] = arg
d[Symbol(letter,:guidefont)] = arg
elseif arg in _allScales
d[Symbol(letter,:scale)] = arg
elseif arg in (:flip, :invert, :inverted)
d[Symbol(letter,:flip)] = true
elseif T <: AbstractString
d[Symbol(letter,:guide)] = arg
# xlims/ylims
elseif (T <: Tuple || T <: AVec) && length(arg) == 2
sym = typeof(arg[1]) <: Number ? :lims : :ticks
d[Symbol(letter,sym)] = arg
# xticks/yticks
elseif T <: AVec
d[Symbol(letter,:ticks)] = arg
elseif arg == nothing
d[Symbol(letter,:ticks)] = []
elseif typeof(arg) <: Number
d[Symbol(letter,:rotation)] = arg
else
warn("Skipped $(letter)axis arg $arg")
end
end
# update an Axis object with magic args and keywords
function update!(axis::Axis, args...; kw...)
# first process args
d = axis.d
for arg in args
process_axis_arg!(d, arg)
end
# then override for any keywords... only those keywords that already exists in d
for (k,v) in kw
if haskey(d, k)
if k == :discrete_values
# add these discrete values to the axis
for vi in v
discrete_value!(axis, vi)
end
else
d[k] = v
end
end
end
# replace scale aliases
if haskey(_scaleAliases, d[:scale])
d[:scale] = _scaleAliases[d[:scale]]
end
axis
end
# -------------------------------------------------------------------------
Base.show(io::IO, axis::Axis) = dumpdict(axis.d, "Axis", true)
# Base.getindex(axis::Axis, k::Symbol) = getindex(axis.d, k)
Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.d, v, ks...)
Base.haskey(axis::Axis, k::Symbol) = haskey(axis.d, k)
Base.extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax))
# get discrete ticks, or not
function get_ticks(axis::Axis)
ticks = axis[:ticks]
dvals = axis[:discrete_values]
if !isempty(dvals) && ticks == :auto
axis[:continuous_values], dvals
else
ticks
end
end
# -------------------------------------------------------------------------
function expand_extrema!(ex::Extrema, v::Number)
ex.emin = min(v, ex.emin)
ex.emax = max(v, ex.emax)
ex
end
function expand_extrema!(axis::Axis, v::Number)
expand_extrema!(axis[:extrema], v)
end
function expand_extrema!{MIN<:Number,MAX<:Number}(axis::Axis, v::Tuple{MIN,MAX})
ex = axis[:extrema]
ex.emin = min(v[1], ex.emin)
ex.emax = max(v[2], ex.emax)
ex
end
function expand_extrema!{N<:Number}(axis::Axis, v::AVec{N})
ex = axis[:extrema]
for vi in v
expand_extrema!(ex, vi)
end
ex
end
function expand_extrema!(sp::Subplot, d::KW)
# first expand for the data
for letter in (:x, :y, :z)
data = d[letter]
axis = sp.attr[Symbol(letter, "axis")]
if eltype(data) <: Number
expand_extrema!(axis, data)
elseif isa(data, Surface) && eltype(data.surf) <: Number
expand_extrema!(axis, data)
elseif data != nothing
# TODO: need more here... gotta track the discrete reference value
# as well as any coord offset (think of boxplot shape coords... they all
# correspond to the same x-value)
# @show letter,eltype(data),typeof(data)
d[letter], d[Symbol(letter,"_discrete_indices")] = discrete_value!(axis, data)
end
end
# # expand for fillrange/bar_width
# fillaxis, baraxis = sp.attr[:yaxis], sp.attr[:xaxis]
# if isvertical(d)
# fillaxis, baraxis = baraxis, fillaxis
# end
# expand for fillrange
vert = isvertical(d)
fr = d[:fillrange]
if fr == nothing && d[:seriestype] == :bar
fr = 0.0
end
if fr != nothing
axis = sp.attr[vert ? :yaxis : :xaxis]
if typeof(fr) <: Tuple
for fri in fr
expand_extrema!(axis, fri)
end
else
expand_extrema!(axis, fr)
end
end
# expand for bar_width
if d[:seriestype] == :bar
dsym = vert ? :x : :y
data = d[dsym]
bw = d[:bar_width]
if bw == nothing
bw = d[:bar_width] = mean(diff(data))
end
# @show data bw
axis = sp.attr[Symbol(dsym, :axis)]
expand_extrema!(axis, maximum(data) + 0.5maximum(bw))
expand_extrema!(axis, minimum(data) - 0.5minimum(bw))
end
end
function expand_extrema!(sp::Subplot, xmin, xmax, ymin, ymax)
expand_extrema!(sp[:xaxis], (xmin, xmax))
expand_extrema!(sp[:yaxis], (ymin, ymax))
end
# -------------------------------------------------------------------------
# push the limits out slightly
function widen(lmin, lmax)
span = lmax - lmin
# eps = max(1e-16, min(1e-2span, 1e-10))
eps = max(1e-16, 0.03span)
lmin-eps, lmax+eps
end
# figure out if widening is a good idea. if there's a scale set it's too tricky,
# so lazy out and don't widen
function default_should_widen(axis::Axis)
should_widen = false
if axis[:scale] == :identity
for series in series_list(axis.sp)
if series.d[:seriestype] in (:scatter,) || series.d[:markershape] != :none
should_widen = true
end
end
end
should_widen
end
# using the axis extrema and limit overrides, return the min/max value for this axis
function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis))
ex = axis[:extrema]
amin, amax = ex.emin, ex.emax
lims = axis[:lims]
if (isa(lims, Tuple) || isa(lims, AVec)) && length(lims) == 2
if isfinite(lims[1])
amin = lims[1]
end
if isfinite(lims[2])
amax = lims[2]
end
end
if amax <= amin && isfinite(amin)
amax = amin + 1.0
end
if should_widen
widen(amin, amax)
else
amin, amax
end
end
# -------------------------------------------------------------------------
# these methods track the discrete (categorical) values which correspond to axis continuous values (cv)
# whenever we have discrete values, we automatically set the ticks to match.
# we return (continuous_value, discrete_index)
function discrete_value!(axis::Axis, dv)
cv_idx = get(axis[:discrete_map], dv, -1)
# @show axis[:discrete_map], axis[:discrete_values], dv
if cv_idx == -1
ex = axis[:extrema]
cv = max(0.5, ex.emax + 1.0)
expand_extrema!(axis, cv)
push!(axis[:discrete_values], dv)
push!(axis[:continuous_values], cv)
cv_idx = length(axis[:discrete_values])
axis[:discrete_map][dv] = cv_idx
cv, cv_idx
else
cv = axis[:continuous_values][cv_idx]
cv, cv_idx
end
end
# continuous value... just pass back with axis negative index
function discrete_value!(axis::Axis, cv::Number)
cv, -1
end
# add the discrete value for each item. return the continuous values and the indices
function discrete_value!(axis::Axis, v::AVec)
n = length(v)
cvec = zeros(n)
discrete_indices = zeros(Int, n)
for i=1:n
cvec[i], discrete_indices[i] = discrete_value!(axis, v[i])
end
cvec, discrete_indices
end
# add the discrete value for each item. return the continuous values and the indices
function discrete_value!(axis::Axis, v::AMat)
n,m = size(v)
cmat = zeros(n,m)
discrete_indices = zeros(Int, n, m)
for i=1:n, j=1:m
cmat[i,j], discrete_indices[i,j] = discrete_value!(axis, v[i,j])
end
cmat, discrete_indices
end
function discrete_value!(axis::Axis, v::Surface)
map(Surface, discrete_value!(axis, v.surf))
end
# -------------------------------------------------------------------------
function pie_labels(sp::Subplot, series::Series)
d = series.d
if haskey(d,:x_discrete_indices)
dvals = sp.attr[:xaxis].d[:discrete_values]
[dvals[idx] for idx in d[:x_discrete_indices]]
else
d[:x]
end
end
+132 -24
View File
@@ -12,16 +12,16 @@ _backend_instance(sym::Symbol) = haskey(_backendType, sym) ? _backendType[sym]()
macro init_backend(s)
str = lowercase(string(s))
sym = symbol(str)
T = symbol(string(s) * "Backend")
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)
$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
@@ -40,17 +40,35 @@ end
@init_backend PGFPlots
include("backends/web.jl")
include("backends/supported.jl")
# include("backends/supported.jl")
# ---------------------------------------------------------
# don't do anything as a default
_create_backend_figure(plt::Plot) = nothing
_prepare_plot_object(plt::Plot) = nothing
_initialize_subplot(plt::Plot, sp::Subplot) = nothing
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")
_series_added(plt::Plot, series::Series) = nothing
_series_updated(plt::Plot, series::Series) = nothing
_before_layout_calcs(plt::Plot) = nothing
title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefont].pointsize * pt
guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefont].pointsize * pt
# Set the (left, top, right, bottom) minimum padding around the plot area
# to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot)
leftpad = 10mm + sp[:left_margin] + guide_padding(sp[:yaxis])
toppad = 2mm + sp[:top_margin] + title_padding(sp)
rightpad = 3mm + sp[:right_margin]
bottompad = 5mm + sp[:bottom_margin] + guide_padding(sp[:xaxis])
# @show (leftpad, toppad, rightpad, bottompad)
sp.minpad = (leftpad, toppad, rightpad, bottompad)
end
_update_plot_object(plt::Plot) = nothing
# ---------------------------------------------------------
@@ -64,14 +82,33 @@ CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym))
# ---------------------------------------------------------
function pickDefaultBackend()
for pkgstr in ("PyPlot", "Immerse", "Qwt", "Gadfly", "GR", "UnicodePlots", "Bokeh", "GLVisualize")
if Pkg.installed(pkgstr) != nothing
return backend(symbol(lowercase(pkgstr)))
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
end
# the default if nothing else is installed
backend(:plotly)
# 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
@@ -111,11 +148,82 @@ end
Set the plot backend.
"""
function backend(pkg::AbstractBackend)
CURRENT_BACKEND.sym = backend_name(pkg)
CURRENT_BACKEND.pkg = pkg
CURRENT_BACKEND.sym = backend_name(pkg)
warn_on_deprecated_backend(CURRENT_BACKEND.sym)
CURRENT_BACKEND.pkg = pkg
end
function backend(modname::Symbol)
CURRENT_BACKEND.sym = modname
CURRENT_BACKEND.pkg = _backend_instance(modname)
warn_on_deprecated_backend(modname)
CURRENT_BACKEND.sym = modname
CURRENT_BACKEND.pkg = _backend_instance(modname)
end
const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse]
function warn_on_deprecated_backend(bsym::Symbol)
if bsym in _deprecated_backends
warn("Backend $bsym has been deprecated. It may not work as originally intended.")
end
end
# ---------------------------------------------------------
supported_types(::AbstractBackend) = []
supported_styles(::AbstractBackend) = [:solid]
supported_markers(::AbstractBackend) = [:none]
supported_scales(::AbstractBackend) = [:identity]
is_subplot_supported(::AbstractBackend) = false
is_string_supported(::AbstractBackend) = false
nativeImagesSupported(b::AbstractBackend) = :image in supported_types(b)
supported_types() = supported_types(backend())
supported_styles() = supported_styles(backend())
supported_markers() = supported_markers(backend())
supported_scales() = supported_scales(backend())
is_subplot_supported() = is_subplot_supported(backend())
is_string_supported() = is_string_supported(backend())
nativeImagesSupported() = nativeImagesSupported(backend())
# ---------------------------------------------------------
# these are args which every backend supports because they're not used in the backend code
const _base_supported_args = [
:color_palette,
:background_color, :background_color_subplot,
:foreground_color, :foreground_color_subplot,
:group,
:seriestype,
:seriescolor, :seriesalpha,
:smooth,
:xerror, :yerror,
:subplot,
:x, :y, :z,
:show, :size,
:margin,
:left_margin,
:right_margin,
:top_margin,
:bottom_margin,
:html_output_format,
:layout,
:link,
:primary,
:series_annotations,
:subplot_index,
:discrete_values,
:projection,
]
function merge_with_base_supported(v::AVec)
v = vcat(v, _base_supported_args)
for vi in v
if haskey(_axis_defaults, vi)
for letter in (:x,:y,:z)
push!(v, Symbol(letter,vi))
end
end
end
v
end
+101 -69
View File
@@ -2,6 +2,74 @@
# https://github.com/bokeh/Bokeh.jl
supported_args(::BokehBackend) = merge_with_base_supported([
# :annotations,
# :axis,
# :background_color,
:linecolor,
# :color_palette,
# :fillrange,
# :fillcolor,
# :fillalpha,
# :foreground_color,
:group,
# :label,
# :layout,
# :legend,
:seriescolor, :seriesalpha,
:linestyle,
:seriestype,
:linewidth,
# :linealpha,
:markershape,
:markercolor,
:markersize,
# :markeralpha,
# :markerstrokewidth,
# :markerstrokecolor,
# :markerstrokestyle,
# :n,
# :bins,
# :nc,
# :nr,
# :pos,
# :smooth,
# :show,
:size,
:title,
# :window_title,
:x,
# :xguide,
# :xlims,
# :xticks,
:y,
# :yguide,
# :ylims,
# :yrightlabel,
# :yticks,
# :xscale,
# :yscale,
# :xflip,
# :yflip,
# :z,
# :tickfont,
# :guidefont,
# :legendfont,
# :grid,
# :surface,
# :levels,
])
supported_types(::BokehBackend) = [:path, :scatter]
supported_styles(::BokehBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
supported_markers(::BokehBackend) = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5]
supported_scales(::BokehBackend) = [:identity, :ln]
is_subplot_supported(::BokehBackend) = false
# --------------------------------------------------------------------------------------
function _initialize_backend(::BokehBackend; kw...)
@eval begin
warn("Bokeh is no longer supported... many features will likely be broken.")
@@ -10,16 +78,9 @@ function _initialize_backend(::BokehBackend; kw...)
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,
:circle => :Circle,
:rect => :Square,
:diamond => :Diamond,
:utriangle => :Triangle,
@@ -35,14 +96,14 @@ const _glyphtypes = KW(
function bokeh_glyph_type(d::KW)
lt = d[:linetype]
st = d[:seriestype]
mt = d[:markershape]
if lt == :scatter && mt == :none
mt = :ellipse
if st == :scatter && mt == :none
mt = :circle
end
# if we have a marker, use that
if lt == :scatter || mt != :none
if st == :scatter || mt != :none
return _glyphtypes[mt]
end
@@ -64,36 +125,30 @@ end
# ---------------------------------------------------------------------------
function _create_plot(pkg::BokehBackend; kw...)
d = KW(kw)
# dumpdict(d, "plot", true)
# function _create_plot(pkg::BokehBackend, d::KW)
function _create_backend_figure(plt::Plot{BokehBackend})
# 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
title = plt.attr[:title]
w, h = plt.attr[:size]
xaxis_type = plt.attr[:xscale] == :log10 ? :log : :auto
yaxis_type = plt.attr[:yscale] == :log10 ? :log : :auto
# legend = plt.attr[: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)
Bokeh.Plot(datacolumns, tools, filename, title, w, h, xaxis_type, yaxis_type, legend) #, extra_args)
Plot(bplt, pkg, 0, d, KW[])
# Plot(bplt, pkg, 0, d, KW[])
end
function _add_series(::BokehBackend, plt::Plot; kw...)
d = KW(kw)
# dumpdict(d, "plot!", true)
bdata = Dict{Symbol, Vector}(:x => collect(d[:x]), :y => collect(d[:y]))
# function _series_added(::BokehBackend, plt::Plot, d::KW)
function _series_added(plt::Plot{BokehBackend}, series::Series)
bdata = Dict{Symbol, Vector}(:x => collect(series.d[:x]), :y => collect(series.d[:y]))
glyph = Bokeh.Bokehjs.Glyph(
glyphtype = bokeh_glyph_type(d),
@@ -107,58 +162,35 @@ function _add_series(::BokehBackend, plt::Plot; kw...)
legend = nothing # TODO
push!(plt.o.datacolumns, Bokeh.BokehDataSet(bdata, glyph, legend))
push!(plt.seriesargs, d)
plt
# 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)
function _update_plot_object(plt::Plot{BokehBackend}, d::KW)
end
# ----------------------------------------------------------------
# accessors for x/y data
function Base.getindex(plt::Plot{BokehBackend}, i::Int)
series = plt.o.datacolumns[i].data
series[:x], series[:y]
end
# 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 Base.setindex!(plt::Plot{BokehBackend}, xy::Tuple, 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
# ----------------------------------------------------------------
@@ -171,6 +203,6 @@ 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
# function Base.display(::PlotsDisplay, plt::Subplot{BokehBackend})
# # TODO: display/show the subplot
# end
+215 -152
View File
@@ -2,6 +2,41 @@
# https://github.com/dcjones/Gadfly.jl
supported_args(::GadflyBackend) = merge_with_base_supported([
:annotations,
:background_color, :foreground_color, :color_palette,
:group, :label, :seriestype,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
:fillrange, :fillcolor, :fillalpha,
:bins, :n, :nc, :nr, :layout, :smooth,
:title, :window_title, :show, :size,
:x, :xguide, :xlims, :xticks, :xscale, :xflip,
:y, :yguide, :ylims, :yticks, :yscale, :yflip,
:z,
:tickfont, :guidefont, :legendfont,
:grid, :legend, :colorbar,
:marker_z, :levels,
:xerror, :yerror,
:ribbon, :quiver,
:orientation,
])
supported_types(::GadflyBackend) = [
:path,
:scatter, :hexbin,
:bar,
:contour, :shape
]
supported_styles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
supported_markers(::GadflyBackend) = vcat(_allMarkers, Shape)
supported_scales(::GadflyBackend) = [:identity, :ln, :log2, :log10, :asinh, :sqrt]
is_subplot_supported(::GadflyBackend) = true
# --------------------------------------------------------------------------------------
function _initialize_backend(::GadflyBackend; kw...)
@eval begin
import Gadfly, Compose
@@ -22,8 +57,8 @@ function createGadflyPlotObject(d::KW)
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]),
gplt.guides = Gadfly.GuideElement[Gadfly.Guide.xlabel(d[:xguide]),
Gadfly.Guide.ylabel(d[:yguide]),
Gadfly.Guide.title(d[:title])]
gplt
end
@@ -32,29 +67,31 @@ end
function getLineGeom(d::KW)
lt = d[:linetype]
xbins, ybins = maketuple(d[:nbins])
if lt == :hexb
st = d[:seriestype]
xbins, ybins = maketuple(d[:bins])
if st == :hexb
Gadfly.Geom.hexbin(xbincount = xbins, ybincount = ybins)
elseif lt == :hist2d
elseif st == :histogram2d
Gadfly.Geom.histogram2d(xbincount = xbins, ybincount = ybins)
elseif lt == :hist
Gadfly.Geom.histogram(bincount = xbins)
elseif lt == :path
elseif st == :histogram
Gadfly.Geom.histogram(bincount = xbins,
orientation = isvertical(d) ? :vertical : :horizontal,
position = d[:bar_position] == :stack ? :stack : :dodge)
elseif st == :path
Gadfly.Geom.path
elseif lt in (:bar, :sticks)
elseif st in (:bar, :sticks)
Gadfly.Geom.bar
elseif lt == :steppost
elseif st == :steppost
Gadfly.Geom.step
elseif lt == :steppre
elseif st == :steppre
Gadfly.Geom.step(direction = :vh)
elseif lt == :hline
elseif st == :hline
Gadfly.Geom.hline
elseif lt == :vline
elseif st == :vline
Gadfly.Geom.vline
elseif lt == :contour
elseif st == :contour
Gadfly.Geom.contour(levels = d[:levels])
# elseif lt == :shape
# elseif st == :shape
# Gadfly.Geom.polygon(fill = true, preserve_order = true)
else
nothing
@@ -77,12 +114,13 @@ function get_extra_theme_args(d::KW, k::Symbol)
end
function getGadflyLineTheme(d::KW)
st = d[:seriestype]
lc = convertColor(getColor(d[:linecolor]), d[:linealpha])
fc = convertColor(getColor(d[:fillcolor]), d[:fillalpha])
Gadfly.Theme(;
default_color = lc,
line_width = (d[:linetype] == :sticks ? 1 : d[:linewidth]) * Gadfly.px,
default_color = (st in (:histogram,:histogram2d,:hexbin,:bar,:sticks) ? fc : lc),
line_width = (st == :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
@@ -96,10 +134,10 @@ function addGadflyLine!(plt::Plot, numlayers::Int, d::KW, geoms...)
gplt = getGadflyContext(plt)
gfargs = vcat(geoms..., getGadflyLineTheme(d))
kwargs = KW()
lt = d[:linetype]
st = d[:seriestype]
# add a fill?
if d[:fillrange] != nothing && lt != :contour
if d[:fillrange] != nothing && st != :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])]
@@ -107,20 +145,20 @@ function addGadflyLine!(plt::Plot, numlayers::Int, d::KW, geoms...)
push!(gfargs, Gadfly.Geom.ribbon)
end
if lt in (:hline, :vline)
kwargs[lt == :hline ? :yintercept : :xintercept] = d[:y]
if st in (:hline, :vline)
kwargs[st == :hline ? :yintercept : :xintercept] = d[:y]
else
if lt == :sticks
if st == :sticks
w = 0.01 * mean(diff(d[:x]))
kwargs[:xmin] = d[:x] - w
kwargs[:xmax] = d[:x] + w
elseif lt == :contour
elseif st == :contour
kwargs[:z] = d[:z].surf
addGadflyContColorScale(plt, d[:linecolor])
end
kwargs[:x] = d[lt == :hist ? :y : :x]
kwargs[:x] = d[st == :histogram ? :y : :x]
kwargs[:y] = d[:y]
end
@@ -132,19 +170,25 @@ end
# ---------------------------------------------------------------------------
get_shape(sym::Symbol) = _shapes[sym]
get_shape(shape::Shape) = shape
# extract the underlying ShapeGeometry object(s)
getMarkerGeom(shape::Shape) = gadflyshape(shape)
getMarkerGeom(shape::Symbol) = gadflyshape(_shapes[shape])
getMarkerGeom(shapes::AVec) = map(getMarkerGeom, shapes)
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
if d[:seriestype] == :shape
Gadfly.Geom.polygon(fill = true, preserve_order = true)
else
getMarkerGeom(d[:markershape])
end
end
function getGadflyMarkerTheme(d::KW, plotargs::KW)
function getGadflyMarkerTheme(d::KW, attr::KW)
c = getColor(d[:markercolor])
α = d[:markeralpha]
if α != nothing
@@ -170,19 +214,19 @@ function getGadflyMarkerTheme(d::KW, plotargs::KW)
end
function addGadflyContColorScale(plt::Plot{GadflyBackend}, c)
plt.plotargs[:colorbar] == :none && return
plt.attr[:colorbar] == :none && return
if !isa(c, ColorGradient)
c = colorscheme(:bluesreds)
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))
function addGadflyMarker!(plt::Plot, numlayers::Int, d::KW, attr::KW, geoms...)
gfargs = vcat(geoms..., getGadflyMarkerTheme(d, attr), getMarkerGeom(d))
kwargs = KW()
# handle continuous color scales for the markers
zcolor = d[:zcolor]
zcolor = d[:marker_z]
if zcolor != nothing && typeof(zcolor) <: AVec
kwargs[:color] = zcolor
addGadflyContColorScale(plt, d[:markercolor])
@@ -195,7 +239,7 @@ end
# ---------------------------------------------------------------------------
function addToGadflyLegend(plt::Plot, d::KW)
if plt.plotargs[:legend] != :none && d[:label] != ""
if plt.attr[:legend] != :none && d[:label] != ""
gplt = getGadflyContext(plt)
# add the legend if needed
@@ -251,21 +295,21 @@ function addGadflySeries!(plt::Plot, d::KW)
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[:linecolor], ColorGradient) || isa(d[:linecolor], ColorFunction))
push!(gplt.scales, Gadfly.Scale.ContinuousColorScale(p -> RGB(getColorZ(d[:linecolor], p))))
elseif lt == :scatter && d[:markershape] == :none
d[:markershape] = :ellipse
st = d[:seriestype]
# if st == :ohlc
# error("Haven't re-implemented after refactoring")
if st in (:histogram2d, :hexbin) && (isa(d[:fillcolor], ColorGradient) || isa(d[:fillcolor], ColorFunction))
push!(gplt.scales, Gadfly.Scale.ContinuousColorScale(p -> RGB(getColorZ(d[:fillcolor], p))))
elseif st == :scatter && d[:markershape] == :none
d[:markershape] = :circle
end
# markers
if d[:markershape] != :none || lt == :shape
prepend!(layers, addGadflyMarker!(plt, length(gplt.layers), d, plt.plotargs, smooth...))
if d[:markershape] != :none || st == :shape
prepend!(layers, addGadflyMarker!(plt, length(gplt.layers), d, plt.attr, smooth...))
end
lt in (:hist2d, :hexbin, :contour) || addToGadflyLegend(plt, d)
st in (:histogram2d, :hexbin, :contour) || addToGadflyLegend(plt, d)
# now save the layers that apply to this series
d[:gadflylayers] = layers
@@ -315,17 +359,25 @@ function addGadflyTicksGuide(gplt, ticks, isx::Bool)
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)
# # 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_discrete : Gadfly.Scale.y_discrete
gfunc = isx ? Gadfly.Scale.x_continuous : Gadfly.Scale.y_continuous
labelmap = Dict(zip(ticks...))
labelfunc = val -> labelmap[val]
push!(gplt.scales, gfunc(levels = collect(ticks[1]), labels = labelfunc))
push!(gplt.scales, gfunc(labels = labelfunc))
else
error("Invalid input for $(isx ? "xticks" : "yticks"): ", ticks)
@@ -341,18 +393,18 @@ function getGadflyScaleFunction(d::KW, isx::Bool)
hasScaleKey = haskey(d, scalekey)
if hasScaleKey
scale = d[scalekey]
scale == :ln && return isx ? Gadfly.Scale.x_log : Gadfly.Scale.y_log, hasScaleKey
scale == :log2 && return isx ? Gadfly.Scale.x_log2 : Gadfly.Scale.y_log2, hasScaleKey
scale == :log10 && return isx ? Gadfly.Scale.x_log10 : Gadfly.Scale.y_log10, hasScaleKey
scale == :asinh && return isx ? Gadfly.Scale.x_asinh : Gadfly.Scale.y_asinh, hasScaleKey
scale == :sqrt && return isx ? Gadfly.Scale.x_sqrt : Gadfly.Scale.y_sqrt, hasScaleKey
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
isx ? Gadfly.Scale.x_continuous : Gadfly.Scale.y_continuous, hasScaleKey, identity
end
function addGadflyLimitsScale(gplt, d::KW, isx::Bool)
gfunc, hasScaleKey = getGadflyScaleFunction(d, isx)
gfunc, hasScaleKey, func = getGadflyScaleFunction(d, isx)
# do we want to add min/max limits for the axis?
limsym = isx ? :xlims : :ylims
@@ -377,18 +429,18 @@ function addGadflyLimitsScale(gplt, d::KW, isx::Bool)
push!(gplt.scales, gfunc(; limargs...))
end
lims
lims, func
end
function updateGadflyAxisFlips(gplt, d::KW, xlims, ylims)
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 : minimum(xlims),
xmax = xlims == nothing ? gplt.coord.xmax : maximum(xlims),
ymin = ylims == nothing ? gplt.coord.ymin : minimum(ylims),
ymax = ylims == nothing ? gplt.coord.ymax : maximum(ylims),
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,
@@ -415,11 +467,11 @@ 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]))
haskey(d, :xguide) && findGuideAndSet(gplt, Gadfly.Guide.xlabel, string(d[:xguide]))
haskey(d, :yguide) && findGuideAndSet(gplt, Gadfly.Guide.ylabel, string(d[:yguide]))
xlims = addGadflyLimitsScale(gplt, d, true)
ylims = addGadflyLimitsScale(gplt, d, false)
xlims, xfunc = addGadflyLimitsScale(gplt, d, true)
ylims, yfunc = addGadflyLimitsScale(gplt, d, false)
ticks = get(d, :xticks, :auto)
if ticks == :none
@@ -434,12 +486,22 @@ function updateGadflyGuides(plt::Plot, d::KW)
addGadflyTicksGuide(gplt, ticks, false)
end
updateGadflyAxisFlips(gplt, d, xlims, ylims)
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
@@ -447,25 +509,24 @@ function updateGadflyPlotTheme(plt::Plot, d::KW)
end
if !get(d, :grid, true)
kwargs[:grid_color] = getColor(d[:background_color])
kwargs[:grid_color] = gridcolor
end
# fonts
tfont, gfont, lfont = d[:tickfont], d[:guidefont], d[:legendfont]
fg = getColor(d[:foreground_color])
getGadflyContext(plt).theme = Gadfly.Theme(;
background_color = getColor(d[:background_color]),
minor_label_color = fg,
background_color = insidecolor,
minor_label_color = textcolor,
minor_label_font = tfont.family,
minor_label_font_size = tfont.pointsize * Gadfly.pt,
major_label_color = fg,
major_label_color = guidecolor,
major_label_font = gfont.family,
major_label_font_size = gfont.pointsize * Gadfly.pt,
key_title_color = fg,
key_title_color = guidecolor,
key_title_font = gfont.family,
key_title_font_size = gfont.pointsize * Gadfly.pt,
key_label_color = fg,
key_label_color = legendcolor,
key_label_font = lfont.family,
key_label_font_size = lfont.pointsize * Gadfly.pt,
plot_padding = 1 * Gadfly.mm,
@@ -507,30 +568,32 @@ end
# ---------------------------------------------------------------------------
# create a blank Gadfly.Plot object
function _create_plot(pkg::GadflyBackend; kw...)
d = KW(kw)
gplt = createGadflyPlotObject(d)
Plot(gplt, pkg, 0, d, KW[])
# function _create_plot(pkg::GadflyBackend, d::KW)
# gplt = createGadflyPlotObject(d)
# Plot(gplt, pkg, 0, d, KW[])
# end
function _create_backend_figure(plt::Plot{GadflyBackend})
createGadflyPlotObject(plt.attr)
end
# plot one data series
function _add_series(::GadflyBackend, plt::Plot; kw...)
# function _series_added(::GadflyBackend, plt::Plot, d::KW)
function _series_added(plt::Plot{GadflyBackend}, series::Series)
# first clear out the temporary layer
gplt = getGadflyContext(plt)
if gplt.layers[1].geom.tag == :remove
gplt.layers = gplt.layers[2:end]
end
d = KW(kw)
addGadflySeries!(plt, d)
push!(plt.seriesargs, d)
plt
addGadflySeries!(plt, series.d)
# push!(plt.seriesargs, d)
# plt
end
function _update_plot(plt::Plot{GadflyBackend}, d::KW)
function _update_plot_object(plt::Plot{GadflyBackend}, d::KW)
updateGadflyGuides(plt, d)
updateGadflyPlotTheme(plt, d)
end
@@ -546,12 +609,12 @@ function getGadflyMappings(plt::Plot, i::Integer)
mappings = [l.mapping for l in plt.seriesargs[i][:gadflylayers]]
end
function Base.getindex(plt::Plot{GadflyBackend}, i::Integer)
function getxy(plt::Plot{GadflyBackend}, i::Integer)
mapping = getGadflyMappings(plt, i)[1]
mapping[:x], mapping[:y]
end
function Base.setindex!(plt::Plot{GadflyBackend}, xy::Tuple, i::Integer)
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
@@ -561,12 +624,12 @@ 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
# # 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)
@@ -586,31 +649,31 @@ end
getGadflyContext(plt::Plot{GadflyBackend}) = plt.o
getGadflyContext(subplt::Subplot{GadflyBackend}) = buildGadflySubplotContext(subplt)
# 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
# # 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]...)
setGadflyDisplaySize(plt::Plot) = setGadflyDisplaySize(plt.attr[:size]...)
# setGadflyDisplaySize(subplt::Subplot) = setGadflyDisplaySize(getattr(subplt, 1)[:size]...)
# -------------------------------------------------------------------------
@@ -638,44 +701,44 @@ end
function Base.display(::PlotsDisplay, plt::Plot{GadflyBackend})
setGadflyDisplaySize(plt.plotargs[:size]...)
setGadflyDisplaySize(plt.attr[: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
# function Base.display(::PlotsDisplay, subplt::Subplot{GadflyBackend})
# setGadflyDisplaySize(getattr(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
+9 -5
View File
@@ -1,8 +1,9 @@
# Geometry which displays arbitrary shapes at given (x, y) positions.
# note: vertices is a list of shapes
immutable ShapeGeometry <: Gadfly.GeometryElement
vertices::AbstractVector{@compat(Tuple{Float64,Float64})}
vertices::AbstractVector #{Tuple{Float64,Float64}}
tag::Symbol
function ShapeGeometry(shape; tag::Symbol=Gadfly.Geom.empty_tag)
@@ -66,24 +67,27 @@ function Gadfly.render(geom::ShapeGeometry, theme::Gadfly.Theme, aes::Gadfly.Aes
end
function gadflyshape(sv::Shape)
ShapeGeometry(sv.vertices)
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 = @compat(Tuple{Compose.Measure, Compose.Measure})
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 geom.vertices]
polys[i] = T[(x + r * sx, y + r * sy) for (sx,sy) in cycle(geom.vertices, i)]
end
Gadfly.polygon(polys, geom.tag)
end
# ---------------------------------------------------------------------------------------------
+74 -84
View File
@@ -2,11 +2,47 @@
# [WEBSITE]
supported_args(::GLVisualizeBackend) = merge_with_base_supported([
# :annotations,
# :background_color_legend, :background_color_inside, :background_color_outside,
# :foreground_color_grid, :foreground_color_legend, :foreground_color_title,
# :foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text,
# :label,
# :linecolor, :linestyle, :linewidth, :linealpha,
# :markershape, :markercolor, :markersize, :markeralpha,
# :markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
# :fillrange, :fillcolor, :fillalpha,
# :bins, :bar_width, :bar_edges, :bar_position,
# :title, :title_location, :titlefont,
# :window_title,
# :guide, :lims, :ticks, :scale, :flip, :rotation,
# :tickfont, :guidefont, :legendfont,
# :grid, :legend, :colorbar,
# :marker_z, :levels,
# :ribbon, :quiver, :arrow,
# :orientation,
# :overwrite_figure,
# :polar,
# :normalize, :weights,
# :contours, :aspect_ratio,
# :match_dimensions,
# :clims,
# :inset_subplots,
])
supported_types(::GLVisualizeBackend) = [:surface]
supported_styles(::GLVisualizeBackend) = [:auto, :solid]
supported_markers(::GLVisualizeBackend) = [:none, :auto, :circle]
supported_scales(::GLVisualizeBackend) = [:identity]
is_subplot_supported(::GLVisualizeBackend) = false
# --------------------------------------------------------------------------------------
function _initialize_backend(::GLVisualizeBackend; kw...)
@eval begin
import GLVisualize
export GLVisualize
end
@eval begin
import GLVisualize
export GLVisualize
end
end
# ---------------------------------------------------------------------------
@@ -15,92 +51,46 @@ immutable GLScreenWrapper
window
end
function _create_plot(pkg::GLVisualizeBackend; kw...)
d = KW(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[])
function _create_backend_figure(plt::Plot{GLVisualizeBackend})
# init a window
window = GLVisualize.glscreen()
@async GLVisualize.renderloop(window)
window
end
function gl_display(plt::Plot{GLVisualizeBackend})
for sp in plt.subplots
# TODO: setup subplot
function _add_series(::GLVisualizeBackend, plt::Plot; kw...)
d = KW(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
for series in series_list(sp)
# TODO: setup series
d = series.d
st = d[:seriestype]
x, y, z = map(Float32, d[:x]), map(Float32, d[:y]), d[:z]
if st == :surface
ismatrix(x) || (x = repmat(x', length(y), 1))
ismatrix(y) || (y = repmat(y, 1, length(x)))
z = transpose_z(d, map(Float32, z.surf), false)
viz = GLVisualize.visualize((x, y, z), :surface)
GLVisualize.view(viz, plt.o)
return
else
error("Series type $st not supported by GLVisualize")
end
end
end
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 Base.getindex(plt::Plot{GLVisualizeBackend}, i::Int)
# TODO:
# series = plt.o.lines[i]
# series.x, series.y
nothing, nothing
end
function Base.setindex!(plt::Plot{GLVisualizeBackend}, xy::Tuple, 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
# function _writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{GLVisualizeBackend})
# # TODO: write a png to io
# end
function _display(plt::Plot{GLVisualizeBackend})
gl_display(plt)
end
+849 -750
View File
File diff suppressed because it is too large Load Diff
+95 -82
View File
@@ -1,6 +1,15 @@
# https://github.com/JuliaGraphics/Immerse.jl
supported_args(::ImmerseBackend) = supported_args(GadflyBackend())
supported_types(::ImmerseBackend) = supported_types(GadflyBackend())
supported_styles(::ImmerseBackend) = supported_styles(GadflyBackend())
supported_markers(::ImmerseBackend) = supported_markers(GadflyBackend())
supported_scales(::ImmerseBackend) = supported_scales(GadflyBackend())
is_subplot_supported(::ImmerseBackend) = true
# --------------------------------------------------------------------------------------
function _initialize_backend(::ImmerseBackend; kw...)
@eval begin
import Immerse, Gadfly, Compose, Gtk
@@ -11,7 +20,7 @@ end
function createImmerseFigure(d::KW)
w,h = d[:size]
figidx = Immerse.figure(; name = d[:windowtitle], width = w, height = h)
figidx = Immerse.figure(; name = d[:window_title], width = w, height = h)
Immerse.Figure(figidx)
end
@@ -19,27 +28,31 @@ end
# create a blank Gadfly.Plot object
function _create_plot(pkg::ImmerseBackend; kw...)
d = KW(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[])
# 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
function _create_backend_figure(plt::Plot{ImmerseBackend})
(nothing, createGadflyPlotObject(plt.attr))
end
# plot one data series
function _add_series(::ImmerseBackend, plt::Plot; kw...)
d = KW(kw)
addGadflySeries!(plt, d)
push!(plt.seriesargs, d)
plt
# # plot one data series
# function _series_added(::ImmerseBackend, plt::Plot, d::KW)
# addGadflySeries!(plt, d)
# push!(plt.seriesargs, d)
# plt
# end
function _series_added(plt::Plot{ImmerseBackend}, series::Series)
addGadflySeries!(plt, series.d)
end
function _update_plot(plt::Plot{ImmerseBackend}, d::KW)
function _update_plot_object(plt::Plot{ImmerseBackend}, d::KW)
updateGadflyGuides(plt, d)
updateGadflyPlotTheme(plt, d)
end
@@ -58,12 +71,12 @@ end
# accessors for x/y data
function Base.getindex(plt::Plot{ImmerseBackend}, i::Integer)
function getxy(plt::Plot{ImmerseBackend}, i::Integer)
mapping = getGadflyMappings(plt, i)[1]
mapping[:x], mapping[:y]
end
function Base.setindex!(plt::Plot{ImmerseBackend}, xy::Tuple, i::Integer)
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
@@ -74,51 +87,51 @@ 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 _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 = getattr(subplt,1)
# w,h = d[:size]
# vsep = Gtk.GtkBoxLeaf(:v)
# win = Gtk.GtkWindowLeaf(vsep, d[:window_title], 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)
@@ -137,14 +150,14 @@ end
# ----------------------------------------------------------------
getGadflyContext(plt::Plot{ImmerseBackend}) = plt.o[2]
getGadflyContext(subplt::Subplot{ImmerseBackend}) = buildGadflySubplotContext(subplt)
# getGadflyContext(subplt::Subplot{ImmerseBackend}) = buildGadflySubplotContext(subplt)
function Base.display(::PlotsDisplay, plt::Plot{ImmerseBackend})
fig, gplt = plt.o
if fig == nothing
fig = createImmerseFigure(plt.plotargs)
fig = createImmerseFigure(plt.attr)
Gtk.on_signal_destroy((x...) -> (Immerse.dropfig(Immerse._display, fig.figno); plt.o = (nothing,gplt)), fig.canvas)
plt.o = (fig, gplt)
end
@@ -154,20 +167,20 @@ function Base.display(::PlotsDisplay, plt::Plot{ImmerseBackend})
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
# 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
+291 -77
View File
@@ -1,107 +1,321 @@
# https://github.com/sisl/PGFPlots.jl
# significant contributions by: @pkofod
supported_args(::PGFPlotsBackend) = merge_with_base_supported([
# :annotations,
# :background_color_legend,
:background_color_inside,
# :background_color_outside,
# :foreground_color_legend, :foreground_color_grid, :foreground_color_axis,
# :foreground_color_text, :foreground_color_border,
:label,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
:fillrange, :fillcolor, :fillalpha,
:bins,
# :bar_width, :bar_edges,
:title,
# :window_title,
:guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont,
:grid, :legend,
# :colorbar,
# :marker_z, :levels,
# :ribbon, :quiver, :arrow,
# :orientation,
# :overwrite_figure,
# :polar,
# :normalize, :weights, :contours,
:aspect_ratio,
# :match_dimensions,
])
supported_types(::PGFPlotsBackend) = [:path, :path3d, :scatter, :steppre, :stepmid, :steppost, :histogram2d, :ysticks, :xsticks, :contour]
supported_styles(::PGFPlotsBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
supported_markers(::PGFPlotsBackend) = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon] #vcat(_allMarkers, Shape)
supported_scales(::PGFPlotsBackend) = [:identity, :ln, :log2, :log10]
is_subplot_supported(::PGFPlotsBackend) = false
# --------------------------------------------------------------------------------------
function _initialize_backend(::PGFPlotsBackend; kw...)
@eval begin
import PGFPlots
export PGFPlots
# TODO: other initialization that needs to be eval-ed
end
# TODO: other initialization
end
# ---------------------------------------------------------------------------
function _create_plot(pkg::PGFPlotsBackend; kw...)
d = KW(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[])
@eval begin
import PGFPlots
export PGFPlots
end
end
function _add_series(::PGFPlotsBackend, plt::Plot; kw...)
d = KW(kw)
# TODO: add one series to the underlying package
push!(plt.seriesargs, d)
plt
# --------------------------------------------------------------------------------------
const _pgfplots_linestyles = KW(
:solid => "solid",
:dash => "dashed",
:dot => "dotted",
:dashdot => "dashdotted",
:dashdotdot => "dashdotdotted",
)
const _pgfplots_markers = KW(
:none => "none",
:cross => "+",
:xcross => "x",
:utriangle => "triangle*",
:dtriangle => "triangle*",
:circle => "*",
:rect => "square*",
:star5 => "star",
:star6 => "asterisk",
:diamond => "diamond*",
:pentagon => "pentagon*",
)
const _pgfplots_legend_pos = KW(
:bottomleft => "south west",
:bottomright => "south east",
:topright => "north east",
:topleft => "north west",
)
const _pgf_series_extrastyle = KW(
:steppre => "const plot mark right",
:stepmid => "const plot mark mid",
:steppost => "const plot",
:sticks => "ycomb",
:ysticks => "ycomb",
:xsticks => "xcomb",
)
# --------------------------------------------------------------------------------------
# takes in color,alpha, and returns color and alpha appropriate for pgf style
function pgf_color(c, a = nothing)
c = getColor(c)
cstr = @sprintf("{rgb,1:red,%.8f;green,%.8f;blue,%.8f}", red(c), green(c), blue(c))
a = float(a == nothing ? alpha(c) : a)
cstr, a
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
function pgf_fillstyle(d::KW)
cstr,a = pgf_color(d[:fillcolor], d[:fillalpha])
"fill = $cstr, fill opacity=$a"
end
function pgf_linestyle(d::KW)
cstr,a = pgf_color(d[:linecolor], d[:linealpha])
"""
color = $cstr,
draw opacity=$a,
line width=$(d[:linewidth]),
$(get(_pgfplots_linestyles, d[:linestyle], "solid"))"""
end
function pgf_marker(d::KW)
shape = d[:markershape]
cstr, a = pgf_color(d[:markercolor], d[:markeralpha])
cstr_stroke, a_stroke = pgf_color(d[:markerstrokecolor], d[:markerstrokealpha])
"""
mark = $(get(_pgfplots_markers, shape, "*")),
mark size = $(0.5 * d[:markersize]),
mark options = {
color = $cstr_stroke, draw opacity = $a_stroke,
fill = $cstr, fill opacity = $a,
line width = $(d[:markerstrokewidth]),
rotate = $(shape == :dtriangle ? 180 : 0),
$(get(_pgfplots_linestyles, d[:markerstrokestyle], "solid"))
}"""
end
# --------------------------------------------------------------------------------------
function pgf_series(sp::Subplot, series::Series)
d = series.d
st = d[:seriestype]
style = []
kw = KW()
push!(style, pgf_linestyle(d))
push!(style, pgf_marker(d))
if d[:fillrange] != nothing
push!(style, pgf_fillstyle(d))
end
# add to legend?
if sp[:legend] != :none && should_add_to_legend(series)
kw[:legendentry] = d[:label]
end
# function args
args = if st == :contour
d[:z].surf, d[:x], d[:y]
elseif is3d(st)
d[:x], d[:y], d[:z]
else
d[:x], d[:y]
end
# PGFPlots can't handle non-Vector?
args = map(a -> if typeof(a) <: AbstractVector && typeof(a) != Vector
collect(a)
else
a
end, args)
# for (i,a) in enumerate(args)
# if typeof(a) <: AbstractVector && typeof(a) != Vector
# args[i] = collect(a)
# end
# end
# include additional style, then add to the kw
if haskey(_pgf_series_extrastyle, st)
push!(style, _pgf_series_extrastyle[st])
end
kw[:style] = join(style, ',')
# build/return the series object
func = if st == :path3d
PGFPlots.Linear3
elseif st == :scatter
PGFPlots.Scatter
elseif st == :histogram2d
PGFPlots.Histogram2
elseif st == :contour
PGFPlots.Contour
else
PGFPlots.Linear
end
func(args...; kw...)
end
# ----------------------------------------------------------------
function _before_update_plot(plt::Plot{PGFPlotsBackend})
end
function pgf_axis(sp::Subplot, letter)
axis = sp[Symbol(letter,:axis)]
style = []
kw = KW()
# TODO: override this to update plot items (title, xlabel, etc) after creation
function _update_plot(plt::Plot{PGFPlotsBackend}, d::KW)
end
# axis guide
kw[Symbol(letter,:label)] = axis[:guide]
function _update_plot_pos_size(plt::AbstractPlot{PGFPlotsBackend}, d::KW)
end
# flip/reverse?
axis[:flip] && push!(style, "$letter dir=reverse")
# ----------------------------------------------------------------
# scale
scale = axis[:scale]
if scale in (:log2, :ln, :log10)
kw[Symbol(letter,:mode)] = "log"
scale == :ln || push!(style, "log basis $letter=$(scale == :log2 ? 2 : 10)")
end
# accessors for x/y data
# limits
# TODO: support zlims
if letter != :z
lims = axis_limits(axis)
kw[Symbol(letter,:min)] = lims[1]
kw[Symbol(letter,:max)] = lims[2]
end
function Base.getindex(plt::Plot{PGFPlotsBackend}, i::Int)
d = plt.seriesargs[i]
d[:x], d[:y]
end
function Base.setindex!(plt::Plot{PGFPlotsBackend}, xy::Tuple, 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
# return the style list and KW args
style, kw
end
# ----------------------------------------------------------------
# ----------------------------------------------------------------
function _make_pgf_plot!(plt::Plot)
plt.o = PGFPlots.Axis[]
for sp in plt.subplots
# first build the PGFPlots.Axis object
style = ["unbounded coords=jump"]
kw = KW()
################# This is the important method to implement!!! #################
function _make_pgf_plot(plt::Plot{PGFPlotsBackend})
# TODO: convert plt.plotargs and plt.seriesargs into PGFPlots calls
# TODO: return the PGFPlots object
# add to style/kw for each axis
for letter in (:x, :y, :z)
if letter != :z || is3d(sp)
axisstyle, axiskw = pgf_axis(sp, letter)
merge!(kw, axiskw)
end
end
# bounding box values are in mm
# note: bb origin is top-left, pgf is bottom-left
bb = bbox(sp)
push!(style, """
xshift = $(left(bb).value)mm,
yshift = $((height(bb) - (bottom(bb))).value)mm,
width = $(width(bb).value)mm,
height = $(height(bb).value)mm,
axis background/.style={fill=$(pgf_color(sp[:background_color_inside])[1])}
""")
if sp[:title] != ""
push!(style, "title = $(sp[:title])")
end
sp[:grid] && push!(style, "grid = major")
if sp[:aspect_ratio] in (1, :equal)
kw[:axisEqual] = "true"
end
legpos = sp[:legend]
if haskey(_pgfplots_legend_pos, legpos)
kw[:legendPos] = _pgfplots_legend_pos[legpos]
end
o = PGFPlots.Axis(; style = style, kw...)
# add the series object to the PGFPlots.Axis
for series in series_list(sp)
push!(o, pgf_series(sp, series))
end
# add the PGFPlots.Axis to the list
push!(plt.o, o)
end
end
function Base.writemime(io::IO, mime::MIME"image/png", plt::AbstractPlot{PGFPlotsBackend})
plt.o = _make_pgf_plot(plt)
writemime(io, mime, plt.o)
function _writemime(io::IO, mime::MIME"image/svg+xml", plt::Plot{PGFPlotsBackend})
_make_pgf_plot!(plt)
writemime(io, mime, plt.o)
end
# function Base.writemime(io::IO, ::MIME"text/html", plt::AbstractPlot{PGFPlotsBackend})
# end
function _writemime(io::IO, mime::MIME"application/pdf", plt::Plot{PGFPlotsBackend})
_make_pgf_plot!(plt)
function Base.display(::PlotsDisplay, plt::AbstractPlot{PGFPlotsBackend})
plt.o = _make_pgf_plot(plt)
display(plt.o)
# prepare the object
pgfplt = PGFPlots.plot(plt.o)
# save a pdf
fn = tempname()*".pdf"
PGFPlots.save(PGFPlots.PDF(fn), pgfplt)
# read it into io
write(io, readall(open(fn)))
# cleanup
PGFPlots.cleanup(plt.o)
end
# function Base.display(::PlotsDisplay, plt::Subplot{PGFPlotsBackend})
# # TODO: display/show the subplot
# end
function _display(plt::Plot{PGFPlotsBackend})
# prepare the object
_make_pgf_plot!(plt)
pgfplt = PGFPlots.plot(plt.o)
# save an svg
fn = string(tempname(), ".svg")
PGFPlots.save(PGFPlots.SVG(fn), pgfplt)
# show it
open_browser_window(fn)
# cleanup
PGFPlots.cleanup(plt.o)
end
+440 -384
View File
@@ -1,13 +1,59 @@
# https://plot.ly/javascript/getting-started
supported_args(::PlotlyBackend) = merge_with_base_supported([
:annotations,
:background_color_legend, :background_color_inside, :background_color_outside,
:foreground_color_legend, :foreground_color_guide,
# :foreground_color_grid, :foreground_color_axis,
:foreground_color_text, :foreground_color_border,
:foreground_color_title,
:label,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
:fillrange, :fillcolor, :fillalpha,
:bins,
:title, :title_location, :titlefont,
:window_title,
:guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont,
:grid, :legend, :colorbar,
:marker_z, :levels,
:ribbon, :quiver,
:orientation,
# :overwrite_figure,
:polar,
:normalize, :weights,
# :contours, :aspect_ratio,
:hover,
:inset_subplots,
])
supported_types(::PlotlyBackend) = [
:path, :scatter, :bar, :pie, :heatmap,
:contour, :surface, :path3d, :scatter3d, :shape, :scattergl,
]
supported_styles(::PlotlyBackend) = [:auto, :solid, :dash, :dot, :dashdot]
supported_markers(::PlotlyBackend) = [
:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle,
:cross, :xcross, :pentagon, :hexagon, :octagon, :vline, :hline
]
supported_scales(::PlotlyBackend) = [:identity, :log10]
is_subplot_supported(::PlotlyBackend) = true
is_string_supported(::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")
_js_code = open(@compat(readstring), _js_path, "r")
# borrowed from https://github.com/plotly/plotly.py/blob/2594076e29584ede2d09f2aa40a8a195b3f3fc66/plotly/offline/offline.py#L64-L71 c/o @spencerlyon2
_js_script = """
@@ -35,228 +81,227 @@ function _initialize_backend(::PlotlyBackend; kw...)
# TODO: other initialization
end
# ---------------------------------------------------------------------------
function _create_plot(pkg::PlotlyBackend; kw...)
d = KW(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; kw...)
d = KW(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 Base.getindex(plt::Plot{PlotlyBackend}, i::Int)
d = plt.seriesargs[i]
d[:x], d[:y]
end
function Base.setindex!(plt::Plot{PlotlyBackend}, xy::Tuple, 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),
function plotly_font(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,
function plotly_annotation_dict(x, y, val; xref="paper", yref="paper")
KW(
:text => val,
:xref => xref,
:x => x,
:yref => xref,
: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,
function plotly_annotation_dict(x, y, ptxt::PlotText; xref="paper", yref="paper")
merge(plotly_annotation_dict(x, y, ptxt.str; xref=xref, yref=yref), KW(
:font => plotly_font(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
# function get_annotation_dict_for_arrow(d::KW, xyprev::Tuple, xy::Tuple, a::Arrow)
# xdiff = xyprev[1] - xy[1]
# ydiff = xyprev[2] - xy[2]
# dist = sqrt(xdiff^2 + ydiff^2)
# KW(
# :showarrow => true,
# :x => xy[1],
# :y => xy[2],
# # :ax => xyprev[1] - xy[1],
# # :ay => xy[2] - xyprev[2],
# # :ax => 0,
# # :ay => -40,
# :ax => 10xdiff / dist,
# :ay => -10ydiff / dist,
# :arrowcolor => webcolor(d[:linecolor], d[:linealpha]),
# :xref => "x",
# :yref => "y",
# :arrowsize => 10a.headwidth,
# # :arrowwidth => a.headlength,
# :arrowwidth => 0.1,
# )
# end
function plotly_scale(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")
# this method gets the start/end in percentage of the canvas for this axis direction
function plotly_domain(sp::Subplot, letter)
figw, figh = sp.plt[:size]
pcts = bbox_to_pcts(sp.plotarea, figw*px, figh*px)
i1,i2 = (letter == :x ? (1,3) : (2,4))
[pcts[i1], pcts[i1]+pcts[i2]]
end
function plotlyaxis(d::KW, isx::Bool)
ax = KW(
:title => d[labelsym(isx)],
:showgrid => d[:grid],
:zeroline => false,
function plotly_axis(axis::Axis, sp::Subplot)
letter = axis[:letter]
ax = KW(
:title => axis[:guide],
:showgrid => sp[: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
if letter in (:x,:y)
ax[:domain] = plotly_domain(sp, letter)
ax[:anchor] = "$(letter==:x ? :y : :x)$(plotly_subplot_index(sp))"
end
# xflip
if d[flipsym(isx)]
ax[:autorange] = "reversed"
rot = axis[:rotation]
if rot != 0
ax[:tickangle] = rot
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
if !(axis[:ticks] in (nothing, :none))
ax[:titlefont] = plotly_font(axis[:guidefont], axis[:foreground_color_guide])
ax[:type] = plotly_scale(axis[:scale])
ax[:tickfont] = plotly_font(axis[:tickfont], axis[:foreground_color_text])
ax[:tickcolor] = webcolor(axis[:foreground_color_border])
ax[:linecolor] = webcolor(axis[:foreground_color_border])
# lims
lims = axis[:lims]
if lims != :auto && limsType(lims) == :limits
ax[:range] = lims
end
# flip
if axis[:flip]
ax[:autorange] = "reversed"
end
# ticks
ticks = axis[:ticks]
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
else
ax[:showticklabels] = false
ax[:showgrid] = false
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()
function plotly_layout(plt::Plot)
d_out = KW()
bgcolor = webcolor(d[:background_color])
fgcolor = webcolor(d[:foreground_color])
w, h = plt[:size]
d_out[:width], d_out[:height] = w, h
d_out[:paper_bgcolor] = webcolor(plt[:background_color_outside])
d_out[:margin] = KW(:l=>0, :b=>0, :r=>0, :t=>20)
# 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
d_out[:annotations] = KW[]
# TODO: x/y axis tick values/labels
d_out[:xaxis] = plotlyaxis(d, true)
d_out[:yaxis] = plotlyaxis(d, false)
for sp in plt.subplots
spidx = plotly_subplot_index(sp)
# legend
d_out[:showlegend] = d[:legend] != :none
if d[:legend] != :none
d_out[:legend] = KW(
:bgcolor => bgcolor,
:bordercolor => fgcolor,
:font => plotlyfont(d[:legendfont]),
)
end
# add an annotation for the title... positioned horizontally relative to plotarea,
# but vertically just below the top of the subplot bounding box
if sp[:title] != ""
bb = plotarea(sp)
tpos = sp[:title_location]
xmm = if tpos == :left
left(bb)
elseif tpos == :right
right(bb)
else
0.5 * (left(bb) + right(bb))
end
titlex, titley = xy_mm_to_pcts(xmm, top(bbox(sp)), w*px, h*px)
titlefont = font(sp[:titlefont], :top, sp[:foreground_color_title])
push!(d_out[:annotations], plotly_annotation_dict(titlex, titley, text(sp[:title], titlefont)))
end
# annotations
anns = get(d, :annotation_list, [])
if !isempty(anns)
d_out[:annotations] = [get_annotation_dict(ann...) for ann in anns]
end
d_out[:plot_bgcolor] = webcolor(sp[:background_color_inside])
d_out
# TODO: x/y axis tick values/labels
# if any(is3d, seriesargs)
if is3d(sp)
d_out[:scene] = KW(
Symbol("xaxis$spidx") => plotly_axis(sp[:xaxis], sp),
Symbol("yaxis$spidx") => plotly_axis(sp[:yaxis], sp),
Symbol("zaxis$spidx") => plotly_axis(sp[:zaxis], sp),
)
else
d_out[Symbol("xaxis$spidx")] = plotly_axis(sp[:xaxis], sp)
d_out[Symbol("yaxis$spidx")] = plotly_axis(sp[:yaxis], sp)
end
# legend
d_out[:showlegend] = sp[:legend] != :none
if sp[:legend] != :none
d_out[:legend] = KW(
:bgcolor => webcolor(sp[:background_color_legend]),
:bordercolor => webcolor(sp[:foreground_color_legend]),
:font => plotly_font(sp[:legendfont], sp[:foreground_color_legend]),
)
end
# annotations
append!(d_out[:annotations], KW[plotly_annotation_dict(ann...; xref = "x$spidx", yref = "y$spidx") for ann in sp[:annotations]])
# # arrows
# for sargs in seriesargs
# a = sargs[:arrow]
# if sargs[:seriestype] in (:path, :line) && typeof(a) <: Arrow
# add_arrows(sargs[:x], sargs[:y]) do xyprev, xy
# push!(d_out[:annotations], get_annotation_dict_for_arrow(sargs, xyprev, xy, a))
# end
# end
# end
if ispolar(sp)
d_out[:direction] = "counterclockwise"
end
d_out
end
# turn off hover if nothing's using it
if all(series -> series.d[:hover] in (false,:none), plt.series_list)
d_out[:hovermode] = "none"
end
d_out
end
function get_plot_json(plt::Plot{PlotlyBackend})
JSON.json(plotly_layout(plt.plotargs))
function plotly_layout_json(plt::Plot)
JSON.json(plotly_layout(plt))
end
function plotly_colorscale(grad::ColorGradient, alpha = nothing)
[[grad.values[i], webcolor(grad.colors[i], alpha)] for i in 1:length(grad.colors)]
[[grad.values[i], webcolor(grad.colors[i], alpha)] for i in 1:length(grad.colors)]
end
plotly_colorscale(c, alpha = nothing) = plotly_colorscale(ColorGradient(:bluesreds), alpha)
plotly_colorscale(c, alpha = nothing) = plotly_colorscale(default_gradient(), alpha)
const _plotly_markers = KW(
:rect => "square",
@@ -266,239 +311,250 @@ const _plotly_markers = KW(
:star5 => "star-triangle-up",
:vline => "line-ns",
:hline => "line-ew",
)
)
function plotly_subplot_index(sp::Subplot)
spidx = sp[:subplot_index]
spidx == 1 ? "" : spidx
end
# the Shape contructor will automatically close the shape. since we need it closed,
# we split by NaNs and then construct/destruct the shapes to get the closed coords
function plotly_close_shapes(x, y)
xs, ys = nansplit(x), nansplit(y)
for i=1:length(xs)
shape = Shape(xs[i], ys[i])
xs[i], ys[i] = shape_coords(shape)
end
nanvcat(xs), nanvcat(ys)
end
# get a dictionary representing the series params (d is the Plots-dict, d_out is the Plotly-dict)
function plotly_series(d::KW; plot_index = nothing)
d_out = KW()
function plotly_series(plt::Plot, series::Series)
d = series.d
sp = d[:subplot]
d_out = KW()
x, y = collect(d[:x]), collect(d[:y])
d_out[:name] = d[:label]
# these are the axes that the series should be mapped to
spidx = plotly_subplot_index(sp)
d_out[:xaxis] = "x$spidx"
d_out[:yaxis] = "y$spidx"
d_out[:showlegend] = should_add_to_legend(series)
lt = d[:linetype]
isscatter = lt in (:scatter, :scatter3d)
hasmarker = isscatter || d[:markershape] != :none
hasline = !isscatter
x, y = collect(d[:x]), collect(d[:y])
d_out[:name] = d[:label]
st = d[:seriestype]
isscatter = st in (:scatter, :scatter3d, :scattergl)
hasmarker = isscatter || d[:markershape] != :none
# hasline = !isscatter
hasline = st in (:path, :path3d)
# 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[:nbins], Tuple)
xbins, ybins = d[:nbins]
else
xbins = ybins = d[:nbins]
end
d_out[:nbinsx] = xbins
d_out[:nbinsy] = ybins
elseif lt in (:hist, :density)
d_out[:type] = "histogram"
isvert = d[:orientation] in (:vertical, :v, :vert)
d_out[isvert ? :x : :y] = y
d_out[isvert ? :nbinsx : :nbinsy] = d[:nbins]
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[:zcolor] != nothing
# d_out[:marker][:color] = d[:zcolor]
# 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[:zcolor])
d_out[:marker][:color] = [webcolor(getColorZ(grad, (zi - zmin) / (zmax - zmin))) for zi in d[:zcolor]]
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"
# set the "type"
if st in (:path, :scatter, :scattergl)
d_out[:type] = st==:scattergl ? "scattergl" : "scatter"
d_out[:mode] = if hasmarker
hasline ? "lines+markers" : "markers"
else
"linear"
end,
:dash => string(d[:linestyle]),
# :dash => "solid",
)
end
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
# # 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
elseif st == :shape
# to draw polygons, we actually draw lines with fill
d_out[:type] = "scatter"
d_out[:mode] = "lines"
d_out[:x], d_out[:y] = plotly_close_shapes(x, y)
# @show map(length, (x,y,d_out[:x],d_out[:y]))
# @show d_out[:x] d_out[:y]
d_out[:fill] = "tozeroy"
d_out[:fillcolor] = webcolor(d[:markercolor], d[:markeralpha])
if d[:markerstrokewidth] > 0
d_out[:line] = KW(
:color => webcolor(d[:markerstrokecolor], d[:markerstrokealpha]),
:width => d[:markerstrokewidth],
:dash => string(d[:markerstrokestyle]),
)
end
d_out
elseif st == :bar
d_out[:type] = "bar"
d_out[:x], d_out[:y] = x, y
d_out[:orientation] = isvertical(d) ? "v" : "h"
# elseif st == :histogram2d
# 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
# d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha])
# elseif st in (:histogram, :density)
# d_out[:type] = "histogram"
# isvert = isvertical(d)
# d_out[isvert ? :x : :y] = y
# d_out[isvert ? :nbinsx : :nbinsy] = d[:bins]
# if st == :density
# d_out[:histogramnorm] = "probability density"
# end
elseif st == :heatmap
d_out[:type] = "heatmap"
d_out[:x], d_out[:y], d_out[:z] = d[:x], d[:y], transpose_z(d, d[:z].surf, false)
d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha])
elseif st == :contour
d_out[:type] = "contour"
d_out[:x], d_out[:y], d_out[:z] = d[:x], d[:y], transpose_z(d, d[:z].surf, false)
# 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 st in (:surface, :wireframe)
d_out[:type] = "surface"
d_out[:x], d_out[:y], d_out[:z] = d[:x], d[:y], transpose_z(d, d[:z].surf, false)
d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha])
elseif st == :pie
d_out[:type] = "pie"
d_out[:labels] = pie_labels(sp, series)
d_out[:values] = y
d_out[:hoverinfo] = "label+percent+name"
elseif st 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: seriestype $st 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 st == :steppre
"vh"
elseif st == :steppost
"hv"
else
"linear"
end,
:dash => string(d[:linestyle]),
# :dash => "solid",
)
end
# convert polar plots x/y to theta/radius
if ispolar(d[:subplot])
d_out[:t] = rad2deg(pop!(d_out, :x))
d_out[:r] = pop!(d_out, :y)
end
# hover text
hover = d[:hover]
if hover in (:none, false)
d_out[:hoverinfo] = "none"
elseif hover != nothing
d_out[:hoverinfo] = "text"
d_out[:text] = hover
end
d_out
end
# get a list of dictionaries, each representing the series params
function get_series_json(plt::Plot{PlotlyBackend})
JSON.json(map(plotly_series, 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, plot_index = i))
end
end
JSON.json(ds)
function plotly_series_json(plt::Plot)
JSON.json(map(series -> plotly_series(plt, series), plt.series_list))
end
# ----------------------------------------------------------------
function html_head(plt::AbstractPlot{PlotlyBackend})
"<script src=\"$(Pkg.dir("Plots","deps","plotly-latest.min.js"))\"></script>"
function html_head(plt::Plot{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
if style == nothing
w, h = plt[: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, $(plotly_series_json(plt)), $(plotly_layout_json(plt)));
</script>
"""
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)));
Plotly.plot(PLOT, $(plotly_series_json(plt)), $(plotly_layout_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})
warn("todo: png")
function _writemime(io::IO, ::MIME"image/png", plt::Plot{PlotlyBackend})
writemime_png_from_html(io, plt)
end
function Base.writemime(io::IO, ::MIME"text/html", plt::AbstractPlot{PlotlyBackend})
function _writemime(io::IO, ::MIME"image/svg+xml", plt::Plot{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)
function _display(plt::Plot{PlotlyBackend})
standalone_html_window(plt)
end
# function Base.display(::PlotsDisplay, plt::Subplot{PlotlyBackend})
# # TODO: display/show the subplot
# end
+35 -89
View File
@@ -1,129 +1,75 @@
# https://github.com/spencerlyon2/PlotlyJS.jl
supported_args(::PlotlyJSBackend) = supported_args(PlotlyBackend())
supported_types(::PlotlyJSBackend) = supported_types(PlotlyBackend())
supported_styles(::PlotlyJSBackend) = supported_styles(PlotlyBackend())
supported_markers(::PlotlyJSBackend) = supported_markers(PlotlyBackend())
is_subplot_supported(::PlotlyJSBackend) = true
is_string_supported(::PlotlyJSBackend) = true
# --------------------------------------------------------------------------------------
function _initialize_backend(::PlotlyJSBackend; kw...)
@eval begin
import PlotlyJS
export PlotlyJS
end
for (mime, fmt) in PlotlyJS._mimeformats
@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
# # override IJulia inline display
# if isijulia()
# IJulia.display_dict(plt::AbstractPlot{PlotlyJSBackend}) = IJulia.display_dict(plt.o)
# end
end
# ---------------------------------------------------------------------------
function _create_plot(pkg::PlotlyJSBackend; kw...)
d = KW(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[])
function _create_backend_figure(plt::Plot{PlotlyJSBackend})
PlotlyJS.plot()
end
function _add_series(::PlotlyJSBackend, plt::Plot; kw...)
d = KW(kw)
function _series_added(plt::Plot{PlotlyJSBackend}, series::Series)
syncplot = plt.o
# dumpdict(d, "addseries", true)
# add to the data array
pdict = plotly_series(d)
pdict = plotly_series(plt, series)
typ = pop!(pdict, :type)
gt = PlotlyJS.GenericTrace(typ; pdict...)
PlotlyJS.addtraces!(syncplot, gt)
# PlotlyJS.addtraces!(syncplot.plot, 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)
function _series_updated(plt::Plot{PlotlyJSBackend}, series::Series)
xsym, ysym = (ispolar(series) ? (:t,:r) : (:x,:y))
PlotlyJS.restyle!(
plt.o,
findfirst(plt.series_list, series),
KW(xsym => (series.d[:x],), ysym => (series.d[:y],))
)
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)
# dumpdict(pdict, "pdict updateplot", true)
function _update_plot_object(plt::Plot{PlotlyJSBackend})
pdict = plotly_layout(plt)
syncplot = plt.o
w,h = d[:size]
w,h = plt[:size]
PlotlyJS.relayout!(syncplot, pdict, width = w, height = h)
# PlotlyJS.relayout!(syncplot.plot, pdict, width = w, height = h)
end
function _update_plot_pos_size(plt::AbstractPlot{PlotlyJSBackend}, d::KW)
end
# ----------------------------------------------------------------
# accessors for x/y data
function Base.getindex(plt::Plot{PlotlyJSBackend}, i::Int)
d = plt.seriesargs[i]
d[:x], d[:y]
function _writemime(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend})
writemime(io, MIME("text/html"), plt.o)
end
function Base.setindex!(plt::Plot{PlotlyJSBackend}, xy::Tuple, i::Integer)
d = plt.seriesargs[i]
d[:x], d[:y] = xy
# TODO: this is likely ineffecient... we should make a call that ONLY changes the plot data
# PlotlyJS.restyle!(plt.o, i, plotly_series(d))
PlotlyJS.restyle!(plt.o, i, KW(:x=>(d[:x],), :y=>(d[:y],)))
plt
function _writemime(io::IO, ::MIME"image/png", plt::Plot{PlotlyJSBackend})
tmpfn = tempname() * "png"
PlotlyJS.savefig(plt.o, tmpfn)
write(io, read(open(tmpfn)))
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.display(::PlotsDisplay, plt::Plot{PlotlyJSBackend})
function _display(plt::Plot{PlotlyJSBackend})
display(plt.o)
end
function Base.display(::PlotsDisplay, plt::Subplot{PlotlyJSBackend})
error()
end
+1124 -813
View File
File diff suppressed because it is too large Load Diff
+94 -65
View File
@@ -1,6 +1,34 @@
# https://github.com/tbreloff/Qwt.jl
supported_args(::QwtBackend) = merge_with_base_supported([
:annotations,
:linecolor,
:fillrange,
:fillcolor,
:label,
:legend,
:seriescolor, :seriesalpha,
:linestyle,
:linewidth,
:markershape,
:markercolor,
:markersize,
:bins,
:pos,
:title,
:window_title,
:guide, :lims, :ticks, :scale,
])
supported_types(::QwtBackend) = [:path, :scatter, :hexbin, :bar]
supported_markers(::QwtBackend) = [:none, :auto, :rect, :circle, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :star8, :hexagon]
supported_scales(::QwtBackend) = [:identity, :log10]
is_subplot_supported(::QwtBackend) = true
# --------------------------------------------------------------------------------------
function _initialize_backend(::QwtBackend; kw...)
@eval begin
warn("Qwt is no longer supported... many features will likely be broken.")
@@ -12,7 +40,7 @@ end
# -------------------------------
@compat const _qwtAliases = KW(
:nbins => :heatmap_n,
:bins => :heatmap_n,
:fillrange => :fillto,
:linewidth => :width,
:markershape => :marker,
@@ -40,29 +68,29 @@ end
function adjustQwtKeywords(plt::Plot{QwtBackend}, iscreating::Bool; kw...)
d = KW(kw)
lt = d[:linetype]
if lt == :scatter
d[:linetype] = :none
st = d[:seriestype]
if st == :scatter
d[:seriestype] = :none
if d[:markershape] == :none
d[:markershape] = :ellipse
d[:markershape] = :circle
end
elseif lt in (:hline, :vline)
elseif st in (:hline, :vline)
addLineMarker(plt, d)
d[:linetype] = :none
d[:markershape] = :ellipse
d[:seriestype] = :none
d[:markershape] = :circle
d[:markersize] = 1
if lt == :vline
if st == :vline
d[:x], d[:y] = d[:y], d[:x]
end
elseif !iscreating && lt == :bar
elseif !iscreating && st == :bar
d = barHack(; kw...)
elseif !iscreating && lt == :hist
elseif !iscreating && st == :histogram
d = barHack(; histogramHack(; kw...)...)
end
replaceQwtAliases(d, :linetype)
replaceQwtAliases(d, :seriestype)
replaceQwtAliases(d, :markershape)
for k in keys(d)
@@ -77,22 +105,23 @@ function adjustQwtKeywords(plt::Plot{QwtBackend}, iscreating::Bool; kw...)
d
end
function _create_plot(pkg::QwtBackend; kw...)
d = KW(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
# function _create_plot(pkg::QwtBackend, d::KW)
function _create_backend_figure(plt::Plot{QwtBackend})
fixcolors(plt.attr)
dumpdict(plt.attr,"\n\n!!! plot")
o = Qwt.plot(zeros(0,0); plt.attr..., show=false)
# plt = Plot(o, pkg, 0, d, KW[])
# plt
end
function _add_series(::QwtBackend, plt::Plot; kw...)
d = adjustQwtKeywords(plt, false; kw...)
# function _series_added(::QwtBackend, plt::Plot, d::KW)
function _series_added(plt::Plot{QwtBackend}, series::Series)
d = adjustQwtKeywords(plt, false; series.d...)
fixcolors(d)
dumpdict(d,"\n\n!!! plot!")
Qwt.oplot(plt.o; d...)
push!(plt.seriesargs, d)
plt
# push!(plt.seriesargs, d)
# plt
end
@@ -132,16 +161,16 @@ function updateLimsAndTicks(plt::Plot{QwtBackend}, d::KW, isx::Bool)
# scaletype == :log && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLogScaleEngine(e))
# scaletype == :log2 && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLogScaleEngine(2))
scaletype == :log10 && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLog10ScaleEngine())
scaletype in supportedScales() || warn("Unsupported scale type: ", scaletype)
scaletype in supported_scales() || warn("Unsupported scale type: ", scaletype)
end
end
function _update_plot(plt::Plot{QwtBackend}, d::KW)
function _update_plot_object(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])
haskey(d, :xguide) && Qwt.xlabel(plt.o, d[:xguide])
haskey(d, :yguide) && Qwt.ylabel(plt.o, d[:yguide])
updateLimsAndTicks(plt, d, true)
updateLimsAndTicks(plt, d, false)
end
@@ -158,7 +187,7 @@ end
function addLineMarker(plt::Plot{QwtBackend}, d::KW)
for yi in d[:y]
marker = Qwt.QWT.QwtPlotMarker()
ishorizontal = (d[:linetype] == :hline)
ishorizontal = (d[:seriestype] == :hline)
marker[:setLineStyle](ishorizontal ? 1 : 2)
marker[ishorizontal ? :setYValue : :setXValue](yi)
qcolor = Qwt.convertRGBToQColor(getColor(d[:linecolor]))
@@ -199,12 +228,12 @@ end
# accessors for x/y data
function Base.getindex(plt::Plot{QwtBackend}, i::Int)
function getxy(plt::Plot{QwtBackend}, i::Int)
series = plt.o.lines[i]
series.x, series.y
end
function Base.setindex!(plt::Plot{QwtBackend}, xy::Tuple, i::Integer)
function setxy!{X,Y}(plt::Plot{QwtBackend}, xy::Tuple{X,Y}, i::Integer)
series = plt.o.lines[i]
series.x, series.y = xy
plt
@@ -217,28 +246,28 @@ end
# -------------------------------
# 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
# # 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, getattr(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
@@ -259,13 +288,13 @@ function Base.writemime(io::IO, ::MIME"image/png", plt::Plot{QwtBackend})
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.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})
@@ -273,9 +302,9 @@ function Base.display(::PlotsDisplay, plt::Plot{QwtBackend})
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
# function Base.display(::PlotsDisplay, subplt::Subplot{QwtBackend})
# for plt in subplt.plts
# Qwt.refresh(plt.o)
# end
# Qwt.showwidget(subplt.o)
# end
-773
View File
@@ -1,773 +0,0 @@
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,
# :axis,
:background_color,
:linecolor,
:color_palette,
:fillrange,
:fillcolor,
:fillalpha,
:foreground_color,
:group,
:label,
:layout,
:legend,
:colorbar,
:linestyle,
:linetype,
:linewidth,
:linealpha,
:markershape,
:markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokealpha,
# :markerstrokestyle,
:n,
:nbins,
:nc,
:nr,
# :pos,
:smooth,
:show,
:size,
:title,
:windowtitle,
:x,
:xlabel,
:xlims,
:xticks,
:y,
:ylabel,
:ylims,
# :yrightlabel,
:yticks,
:xscale,
:yscale,
:xflip,
:yflip,
:z,
:zcolor,
:tickfont,
:guidefont,
:legendfont,
:grid,
# :surface,
:levels,
]
supportedAxes(::GadflyBackend) = [:auto, :left]
supportedTypes(::GadflyBackend) = [:none, :line, :path, :steppre, :steppost, :sticks,
:scatter, :hist2d, :hexbin, :hist, :bar,
: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,
:axis,
:background_color,
:linecolor,
:color_palette,
:fillrange,
:fillcolor,
:foreground_color,
:group,
:label,
:layout,
:legend,
:colorbar,
:linestyle,
:linetype,
:linewidth,
:markershape,
:markercolor,
:markersize,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokealpha,
# :markerstrokestyle,
:n,
:nbins,
:nc,
:nr,
# :pos,
:smooth,
# :ribbon,
:show,
:size,
:title,
:windowtitle,
:x,
:xlabel,
:xlims,
:xticks,
:y,
:ylabel,
:ylims,
:zlims,
:yrightlabel,
:yticks,
:xscale,
:yscale,
:xflip,
:yflip,
:z,
:zcolor, # only supported for scatter/scatter3d
:tickfont,
:guidefont,
:legendfont,
:grid,
# :surface,
:levels,
:fillalpha,
:linealpha,
:markeralpha,
:overwrite_figure,
]
supportedAxes(::PyPlotBackend) = _allAxes
supportedTypes(::PyPlotBackend) = [:none, :line, :path, :steppre, :steppost, #:sticks,
:scatter, :hist2d, :hexbin, :hist, :density, :bar,
:hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe, :heatmap]
supportedStyles(::PyPlotBackend) = [:auto, :solid, :dash, :dot, :dashdot]
# supportedMarkers(::PyPlotBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon]
supportedMarkers(::PyPlotBackend) = vcat(_allMarkers, Shape)
supportedScales(::PyPlotBackend) = [:identity, :ln, :log2, :log10]
subplotSupported(::PyPlotBackend) = true
# --------------------------------------------------------------------------------------
supportedArgs(::GRBackend) = [
:annotation,
:axis,
:background_color,
:linecolor,
:color_palette,
:fillrange,
:fillcolor,
:foreground_color,
:group,
:label,
:layout,
:legend,
:colorbar,
:linestyle,
:linetype,
:linewidth,
:markershape,
:markercolor,
:markersize,
:markerstrokewidth,
:markerstrokecolor,
# :markerstrokestyle,
:n,
:nbins,
:nc,
:nr,
# :pos,
:smooth,
# :ribbon,
:show,
:size,
:title,
:windowtitle,
:x,
:xlabel,
:xlims,
:xticks,
:y,
:ylabel,
:ylims,
:yrightlabel,
:yticks,
:xscale,
:yscale,
:xflip,
:yflip,
:z,
:zcolor, # only supported for scatter/scatter3d
:tickfont,
:guidefont,
:legendfont,
:grid,
# :surface,
:nlevels,
:fillalpha,
:linealpha,
:markeralpha,
]
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,
# :args,
:axis,
:background_color,
:linecolor,
:color_palette,
:fillrange,
:fillcolor,
:foreground_color,
:group,
# :heatmap_c,
# :kwargs,
:label,
:layout,
:legend,
:linestyle,
:linetype,
:linewidth,
:markershape,
:markercolor,
:markersize,
# :markerstrokewidth,
# :markerstrokecolor,
# :markerstrokestyle,
:n,
:nbins,
:nc,
:nr,
:pos,
:smooth,
# :ribbon,
:show,
:size,
:title,
:windowtitle,
:x,
:xlabel,
:xlims,
:xticks,
:y,
:ylabel,
:ylims,
:yrightlabel,
:yticks,
:xscale,
:yscale,
# :xflip,
# :yflip,
# :z,
]
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,
:linestyle,
:linetype,
# :linewidth,
:markershape,
# :markercolor,
# :markersize,
# :markerstrokewidth,
# :markerstrokecolor,
# :markerstrokestyle,
# :n,
:nbins,
# :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,
:linestyle,
:linetype,
:linewidth,
:markershape,
:markercolor,
:markersize,
# :markerstrokewidth,
# :markerstrokecolor,
# :markerstrokestyle,
# :n,
:nbins,
# :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,
:linestyle,
:linetype,
:linewidth,
# :linealpha,
:markershape,
:markercolor,
:markersize,
# :markeralpha,
# :markerstrokewidth,
# :markerstrokecolor,
# :markerstrokestyle,
# :n,
# :nbins,
# :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,
:linecolor,
:linestyle,
:linetype,
:linewidth,
:linealpha,
:markershape,
:markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokestyle,
:n,
:nbins,
:nc,
:nr,
# :pos,
# :smooth,
:show,
:size,
:title,
:windowtitle,
:x,
:xlabel,
:xlims,
:xticks,
:y,
:ylabel,
:ylims,
# :yrightlabel,
:yticks,
:xscale,
:yscale,
:xflip,
:yflip,
:z,
:zcolor,
:tickfont,
:guidefont,
:legendfont,
:grid,
:levels,
]
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,
:linecolor,
:linestyle,
:linetype,
:linewidth,
:linealpha,
:markershape,
:markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokestyle,
:n,
:nbins,
:nc,
:nr,
# :pos,
# :smooth,
:show,
:size,
:title,
:windowtitle,
:x,
:xlabel,
:xlims,
:xticks,
:y,
:ylabel,
:ylims,
# :yrightlabel,
:yticks,
:xscale,
:yscale,
:xflip,
:yflip,
:z,
:zcolor,
:tickfont,
:guidefont,
:legendfont,
:grid,
:levels,
]
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
# :linewidth,
# :linealpha,
# :markershape,
# :markercolor,
# :markersize,
# :markeralpha,
# :markerstrokewidth,
# :markerstrokecolor,
# :markerstrokestyle,
# :n,
# :nbins,
# :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,
# :linecolor,
# :linestyle,
# :linetype,
# :linewidth,
# :linealpha,
# :markershape,
# :markercolor,
# :markersize,
# :markeralpha,
# :markerstrokewidth,
# :markerstrokecolor,
# :markerstrokestyle,
# :n,
# :nbins,
# :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(::PGFPlotsBackend) = [:auto, :left]
supportedTypes(::PGFPlotsBackend) = [:contour] #, :path, :scatter ,:steppre, :steppost, :sticks, :hist2d, :hexbin, :hist, :bar, :hline, :vline, :contour]
supportedStyles(::PGFPlotsBackend) = [:auto, :solid] #, :dash, :dot, :dashdot, :dashdotdot]
supportedMarkers(::PGFPlotsBackend) = [:none, :auto, :ellipse] #, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5] #vcat(_allMarkers, Shape)
supportedScales(::PGFPlotsBackend) = [:identity] #, :log, :log2, :log10, :asinh, :sqrt]
subplotSupported(::PGFPlotsBackend) = false
+57 -71
View File
@@ -1,89 +1,75 @@
# TODO: find/replace all [PkgName] with CamelCase, all [pkgname] with lowercase
# TODO: find/replace all [PkgName] with CamelCase
# [WEBSITE]
# [ADD BACKEND 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
function _initialize_backend(::[PkgName]Backend; kw...)
@eval begin
import [PkgName]
export [PkgName]
# todo: other initialization that needs to be eval-ed
end
# todo: other initialization
end
# ---------------------------------------------------------------------------
function _create_plot(pkg::[PkgName]AbstractBackend; kw...)
d = KW(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[])
# Create the window/figure for this backend.
function _create_backend_figure(plt::Plot{[PkgName]Backend})
nothing
end
# # this is called early in the pipeline, use it to make the plot current or something
# function _prepare_plot_object(plt::Plot{[PkgName]Backend})
# end
# Set up the subplot within the backend object.
function _initialize_subplot(plt::Plot{[PkgName]Backend}, sp::Subplot{[PkgName]Backend})
end
# ---------------------------------------------------------------------------
# Add one series to the underlying backend object.
function _series_added(plt::Plot{[PkgName]Backend}, series::Series)
end
# When series data is added/changed, this callback can do dynamic updates to the backend object.
# note: if the backend rebuilds the plot from scratch on display, then you might not do anything here.
function _series_updated(plt::Plot{[PkgName]Backend}, series::Series)
end
# ---------------------------------------------------------------------------
# called just before updating layout bounding boxes... in case you need to prep
# for the calcs
function _before_layout_calcs(plt::Plot{[PkgName]Backend})
end
# Set the (left, top, right, bottom) minimum padding around the plot area
# to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot{[PkgName]Backend})
sp.minpad = (20mm, 5mm, 2mm, 10mm)
end
function _add_series(::[PkgName]AbstractBackend, plt::Plot; kw...)
d = KW(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
# Override this to update plot items (title, xlabel, etc), and add annotations (d[:annotations])
function _update_plot_object(plt::Plot{[PkgName]Backend})
end
# ----------------------------------------------------------------
function _before_update_plot(plt::Plot{[PkgName]AbstractBackend})
# Write a png to io. You could define methods for:
# "application/eps" => "eps",
# "image/eps" => "eps",
# "application/pdf" => "pdf",
# "image/png" => "png",
# "application/postscript" => "ps",
# "image/svg+xml" => "svg"
function _writemime(io::IO, ::MIME"image/png", plt::Plot{[PkgName]Backend})
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 Base.getindex(plt::Plot{[PkgName]AbstractBackend}, i::Int)
# TODO: return a tuple of (x, y) vectors
end
function Base.setindex!(plt::Plot{[PkgName]AbstractBackend}, xy::Tuple, 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
# Display/show the plot (open a GUI window, or browser page, for example).
function _display(plt::Plot{[PkgName]Backend})
end
+197 -107
View File
@@ -1,6 +1,32 @@
# https://github.com/Evizero/UnicodePlots.jl
supported_args(::UnicodePlotsBackend) = merge_with_base_supported([
:label,
:legend,
:seriescolor, :seriesalpha,
:linestyle,
:markershape,
:bins,
:title,
:window_title,
:guide, :lims,
])
supported_types(::UnicodePlotsBackend) = [
:path, :scatter,
:histogram2d
]
supported_styles(::UnicodePlotsBackend) = [:auto, :solid]
supported_markers(::UnicodePlotsBackend) = [:none, :auto, :circle]
supported_scales(::UnicodePlotsBackend) = [:identity]
is_subplot_supported(::UnicodePlotsBackend) = true
# don't warn on unsupported... there's just too many warnings!!
warnOnUnsupported_args(pkg::UnicodePlotsBackend, d::KW) = nothing
# --------------------------------------------------------------------------------------
function _initialize_backend(::UnicodePlotsBackend; kw...)
@eval begin
import UnicodePlots
@@ -10,93 +36,149 @@ end
# -------------------------------
# convert_size_from_pixels(sz) =
# 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)
plt.o = []
for sp in plt.subplots
xaxis = sp[:xaxis]
yaxis = sp[:yaxis]
xlim = axis_limits(xaxis)
ylim = axis_limits(yaxis)
# figure out the plotting area xlim = [xmin, xmax] and ylim = [ymin, ymax]
sargs = plt.seriesargs
iargs = plt.plotargs
# make vectors
xlim = [xlim[1], xlim[2]]
ylim = [ylim[1], ylim[2]]
# get the x/y limits
if get(iargs, :xlims, :auto) == :auto
xlim = [Inf, -Inf]
for d in sargs
_expand_limits(xlim, d[:x])
# 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 = plt[:size]
o = UnicodePlots.Plot(x, y;
width = width,
height = height,
title = sp[:title],
xlim = xlim,
ylim = ylim
)
# set the axis labels
UnicodePlots.xlabel!(o, xaxis[:guide])
UnicodePlots.ylabel!(o, yaxis[:guide])
# now use the ! functions to add to the plot
for series in series_list(sp)
addUnicodeSeries!(o, series.d, sp[:legend] != :none, xlim, ylim)
end
# save the object
push!(plt.o, o)
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
# # 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.attr
#
# # 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[:xguide])
# UnicodePlots.ylabel!(o, iargs[:yguide])
#
# # 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]
# get the function, or special handling for step/bar/hist
st = d[:seriestype]
# handle hline/vline separately
if st in (:hline,:vline)
for yi in d[:y]
if st == :hline
UnicodePlots.lineplot!(o, xlim, [yi,yi])
else
UnicodePlots.lineplot!(o, [yi,yi], ylim)
end
end
return
# elseif st == :bar
# UnicodePlots.barplot!(o, d[:x], d[:y])
# return
# elseif st == :histogram
# UnicodePlots.histogram!(o, d[:y], bins = d[:bins])
# return
elseif st == :histogram2d
UnicodePlots.densityplot!(o, d[:x], d[:y])
return
# 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
if st == :path
func = UnicodePlots.lineplot!
elseif lt == :scatter || d[:markershape] != :none
elseif st == :scatter || d[:markershape] != :none
func = UnicodePlots.scatterplot!
elseif lt == :steppost
elseif st == :steppost
func = UnicodePlots.stairs!
elseif lt == :steppre
elseif st == :steppre
func = UnicodePlots.stairs!
stepstyle = :pre
else
error("Linestyle $lt not supported by UnicodePlots")
error("Linestyle $st not supported by UnicodePlots")
end
# get the series data and label
@@ -111,57 +193,64 @@ function addUnicodeSeries!(o, d::KW, addlegend::Bool, xlim, ylim)
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 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; kw...)
plt = Plot(nothing, pkg, 0, KW(kw), KW[])
# function _create_plot(pkg::UnicodePlotsBackend, d::KW)
# plt = Plot(nothing, pkg, 0, d, KW[])
function _create_backend_figure(plt::Plot{UnicodePlotsBackend})
# 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
# if !haskey(plt.attr, :size) || plt.attr[:size] == default(:size)
# plt.attr[:size] = (60,20)
# end
w, h = plt[:size]
plt.attr[:size] = div(w, 10), div(h, 20)
plt.attr[:color_palette] = [RGB(0,0,0)]
nothing
plt
# plt
end
function _add_series(::UnicodePlotsBackend, plt::Plot; kw...)
d = KW(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
# function _series_added(plt::Plot{UnicodePlotsBackend}, series::Series)
# d = series.d
# # TODO don't need these once the "bar" series recipe is done
# if d[:seriestype] in (:sticks, :bar)
# d = barHack(; d...)
# elseif d[:seriestype] == :histogram
# d = barHack(; histogramHack(; d...)...)
# end
# # push!(plt.seriesargs, d)
# # plt
# end
#
#
# function _update_plot_object(plt::Plot{UnicodePlotsBackend}, d::KW)
# for k in (:title, :xguide, :yguide, :xlims, :ylims)
# if haskey(d, k)
# plt.attr[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))
function png(plt::AbstractPlot{UnicodePlotsBackend}, fn::AbstractString)
fn = addExtension(fn, "png")
# make some whitespace and show the plot
println("\n\n\n\n\n\n")
gui(plt)
@osx_only begin
# @osx_only begin
@compat @static if is_apple()
# BEGIN HACK
# wait while the plot gets drawn
@@ -182,21 +271,22 @@ 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 _create_subplot(subplt::Subplot{UnicodePlotsBackend}, isbefore::Bool)
# isbefore && return false
# true
# end
function Base.display(::PlotsDisplay, plt::Plot{UnicodePlotsBackend})
function _display(plt::Plot{UnicodePlotsBackend})
rebuildUnicodePlot!(plt)
show(plt.o)
map(show, plt.o)
nothing
end
function Base.display(::PlotsDisplay, subplt::Subplot{UnicodePlotsBackend})
for plt in subplt.plts
gui(plt)
end
end
# function Base.display(::PlotsDisplay, subplt::Subplot{UnicodePlotsBackend})
# for plt in subplt.plts
# gui(plt)
# end
# end
+35 -7
View File
@@ -4,7 +4,7 @@
# 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"))
function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.attr, :window_title, "Plots.jl"))
"""
<!DOCTYPE html>
<html>
@@ -20,19 +20,47 @@ function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.plot
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)`)
@compat @static if is_apple()
return run(`open $(filename)`)
end
@compat @static if is_linux()
return run(`xdg-open $(filename)`)
end
@compat @static if is_windows()
return run(`$(ENV["COMSPEC"]) /c start $(filename)`)
end
warn("Unknown OS... cannot open browser window.")
end
function standalone_html_window(plt::AbstractPlot; kw...)
html = standalone_html(plt; kw...)
# println(html)
function write_temp_html(plt::AbstractPlot)
html = standalone_html(plt; title = plt.attr[:window_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.attr[: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
+69 -42
View File
@@ -3,6 +3,34 @@
# credit goes to https://github.com/jverzani for contributing to the first draft of this backend implementation
supported_args(::WinstonBackend) = merge_with_base_supported([
:annotations,
:linecolor,
:fillrange,
:fillcolor,
:label,
:legend,
:seriescolor, :seriesalpha,
:linestyle,
:linewidth,
:markershape,
:markercolor,
:markersize,
:bins,
:title,
:window_title,
:guide, :lims, :scale,
])
supported_types(::WinstonBackend) = [:path, :scatter, :bar]
supported_styles(::WinstonBackend) = [:auto, :solid, :dash, :dot, :dashdot]
supported_markers(::WinstonBackend) = [:none, :auto, :rect, :circle, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5]
supported_scales(::WinstonBackend) = [:identity, :log10]
is_subplot_supported(::WinstonBackend) = false
# --------------------------------------------------------------------------------------
function _initialize_backend(::WinstonBackend; kw...)
@eval begin
# ENV["WINSTON_OUTPUT"] = "gtk"
@@ -24,7 +52,7 @@ end
@compat const winston_marker = KW(:none=>".",
:rect => "square",
:ellipse=>"circle",
:circle=>"circle",
:diamond=>"diamond",
:utriangle=>"triangle",
:dtriangle=>"down-triangle",
@@ -33,18 +61,18 @@ end
:star5 => "asterisk"
)
function _before_add_series(plt::Plot{WinstonBackend})
function _before_update(plt::Plot{WinstonBackend})
Winston.ghf(plt.o)
end
# ---------------------------------------------------------------------------
function _create_plot(pkg::WinstonBackend; kw...)
d = KW(kw)
wplt = Winston.FramedPlot(title = d[:title], xlabel = d[:xlabel], ylabel = d[:ylabel])
Plot(wplt, pkg, 0, d, KW[])
function _create_backend_figure(plt::Plot{WinstonBackend})
Winston.FramedPlot(
title = plt.attr[:title],
xlabel = plt.attr[:xguide],
ylabel = plt.attr[:yguide]
)
end
copy_remove(d::KW, s::Symbol) = delete!(copy(d), s)
@@ -64,13 +92,12 @@ function getWinstonItems(plt::Plot)
window, canvas, wplt
end
function _add_series(::WinstonBackend, plt::Plot; kw...)
d = KW(kw)
function _series_added(plt::Plot{WinstonBackend}, series::Series)
d = series.d
window, canvas, wplt = getWinstonItems(plt)
# until we call it normally, do the hack
if d[:linetype] == :bar
if d[:seriestype] == :bar
d = barHack(;d...)
end
@@ -88,11 +115,11 @@ function _add_series(::WinstonBackend, plt::Plot; kw...)
## lintype :path, :step, :stepinverted, :sticks, :dots, :none, :hist2d, :hexbin, :hist, :bar
if d[:linetype] == :none
## lintype :path, :step, :stepinverted, :sticks, :dots, :none, :histogram2d, :hexbin, :histogram, :bar
if d[:seriestype] == :none
Winston.add(wplt, Winston.Points(d[:x], d[:y]; copy_remove(e, :kind)..., color=getColor(d[:markercolor])))
elseif d[:linetype] == :path
elseif d[:seriestype] == :path
x, y = d[:x], d[:y]
Winston.add(wplt, Winston.Curve(x, y; e...))
@@ -106,38 +133,38 @@ function _add_series(::WinstonBackend, plt::Plot; kw...)
Winston.add(wplt, Winston.FillBetween(x, y, x, y2, fillcolor=getColor(d[:fillcolor])))
end
elseif d[:linetype] == :scatter
elseif d[:seriestype] == :scatter
if d[:markershape] == :none
d[:markershape] = :ellipse
d[:markershape] = :circle
end
# elseif d[:linetype] == :step
# elseif d[:seriestype] == :step
# fn = Winston.XXX
# elseif d[:linetype] == :stepinverted
# elseif d[:seriestype] == :stepinverted
# fn = Winston.XXX
elseif d[:linetype] == :sticks
elseif d[:seriestype] == :sticks
Winston.add(wplt, Winston.Stems(d[:x], d[:y]; e...))
# elseif d[:linetype] == :dots
# elseif d[:seriestype] == :dots
# fn = Winston.XXX
# elseif d[:linetype] == :hist2d
# elseif d[:seriestype] == :histogram2d
# fn = Winston.XXX
# elseif d[:linetype] == :hexbin
# elseif d[:seriestype] == :hexbin
# fn = Winston.XXX
elseif d[:linetype] == :hist
hst = hist(d[:y], d[:nbins])
Winston.add(wplt, Winston.Histogram(hst...; copy_remove(e, :nbins)...))
elseif d[:seriestype] == :histogram
hst = hist(d[:y], d[:bins])
Winston.add(wplt, Winston.Histogram(hst...; copy_remove(e, :bins)...))
# elseif d[:linetype] == :bar
# elseif d[:seriestype] == :bar
# # fn = Winston.XXX
else
error("linetype $(d[:linetype]) not supported by Winston.")
error("seriestype $(d[:seriestype]) not supported by Winston.")
end
@@ -149,10 +176,10 @@ function _add_series(::WinstonBackend, plt::Plot; kw...)
# optionally add a regression line
d[:smooth] && d[:linetype] != :hist && addRegressionLineWinston(d, wplt)
d[:smooth] && d[:seriestype] != :histogram && addRegressionLineWinston(d, wplt)
push!(plt.seriesargs, d)
plt
# push!(plt.seriesargs, d)
# plt
end
@@ -165,9 +192,9 @@ end
:yscale => :ylog,
)
function _update_plot(plt::Plot{WinstonBackend}, d::KW)
function _update_plot_object(plt::Plot{WinstonBackend}, d::KW)
window, canvas, wplt = getWinstonItems(plt)
for k in (:xlabel, :ylabel, :title, :xlims, :ylims)
for k in (:xguide, :yguide, :title, :xlims, :ylims)
if haskey(d, k)
Winston.setattr(wplt, string(get(_winstonNames, k, k)), d[k])
end
@@ -199,14 +226,14 @@ 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 _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
if plt.attr[:legend] != :none
Winston.legend(wplt, [sd[:label] for sd in plt.seriesargs])
end
end
@@ -227,9 +254,9 @@ function Base.display(::PlotsDisplay, plt::Plot{WinstonBackend})
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]
w,h = plt.attr[:size]
canvas = Gtk.GtkCanvasLeaf()
window = Gtk.GtkWindowLeaf(canvas, plt.plotargs[:windowtitle], w, h)
window = Gtk.GtkWindowLeaf(canvas, plt.attr[:window_title], w, h)
plt.o = (window, canvas, wplt)
end
@@ -240,6 +267,6 @@ function Base.display(::PlotsDisplay, plt::Plot{WinstonBackend})
end
function Base.display(::PlotsDisplay, subplt::Subplot{WinstonBackend})
# TODO: display/show the Subplot object
end
# function Base.display(::PlotsDisplay, subplt::Subplot{WinstonBackend})
# # TODO: display/show the Subplot object
# end
File diff suppressed because it is too large Load Diff
+27 -40
View File
@@ -18,10 +18,13 @@ colorscheme(c::Colorant; kw...) = ColorWrapper(c; kw...)
# --------------------------------------------------------------
convertColor(c::@compat(Union{AbstractString, Symbol})) = parse(Colorant, string(c))
convertColor(c::AbstractString) = parse(Colorant, c)
convertColor(c::Symbol) = parse(Colorant, string(c))
convertColor(c::Colorant) = c
convertColor(cvec::AbstractVector) = map(convertColor, cvec)
convertColor(c::ColorScheme) = c
convertColor(v::Void) = RGBA(0,0,0,0)
convertColor(b::Bool) = b ? RGBA(0,0,0,1) : RGBA(0,0,0,0)
function convertColor(c, α::Real)
c = convertColor(c)
@@ -52,7 +55,7 @@ const _rainbowColors = [colorant"blue", colorant"purple", colorant"green", color
const _testColors = [colorant"darkblue", colorant"blueviolet", colorant"darkcyan",colorant"green",
darken(colorant"yellow",0.3), colorant"orange", darken(colorant"red",0.2)]
@compat const _gradients = KW(
const _gradients = KW(
:blues => [colorant"lightblue", colorant"darkblue"],
:reds => [colorant"lightpink", colorant"darkred"],
:greens => [colorant"lightgreen", colorant"darkgreen"],
@@ -67,6 +70,13 @@ const _testColors = [colorant"darkblue", colorant"blueviolet", colorant"darkcya
: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)
# --------------------------------------------------------------
@@ -107,9 +117,14 @@ end
# ColorGradient(map(convertColor, cs), vals; kw...)
# end
# function ColorGradient(grad::ColorGradient; alpha = nothing)
# ColorGradient(convertColor(grad.colors, alpha), grad.values)
# end
function ColorGradient(grad::ColorGradient; alpha = nothing)
ColorGradient(convertColor(grad.colors, alpha), grad.values)
end
# anything else just gets the default gradient
function ColorGradient(cw; alpha=nothing)
ColorGradient(default_gradient(), alpha=alpha)
end
getColor(gradient::ColorGradient, idx::Int) = gradient.colors[mod1(idx, length(gradient.colors))]
@@ -287,7 +302,7 @@ function generate_colorgradient(bgcolor = colorant"white";
gradient_from_list(colors)
end
function get_color_palette(palette, bgcolor::@compat(Union{Colorant,ColorWrapper}), numcolors::Integer)
function get_color_palette(palette, bgcolor::Union{Colorant,ColorWrapper}, numcolors::Integer)
grad = if palette == :auto
generate_colorgradient(bgcolor)
else
@@ -297,7 +312,8 @@ function get_color_palette(palette, bgcolor::@compat(Union{Colorant,ColorWrapper
RGBA[getColorZ(grad, z) for z in zrng]
end
function get_color_palette(palette::Vector{RGBA}, bgcolor::@compat(Union{Colorant,ColorWrapper}), numcolors::Integer)
function get_color_palette{C<:Colorant}(palette::Vector{C},
bgcolor::Union{Colorant,ColorWrapper}, numcolors::Integer)
palette
end
@@ -340,10 +356,10 @@ 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]]...)
@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))
@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))
@@ -351,41 +367,12 @@ 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
# bgcolor
d[:background_color] = colorscheme(bgcolor)
d[:foreground_color] = colorscheme(fgcolor)
end
# converts a symbol or string into a colorant (Colors.RGB), and assigns a color automatically
function getSeriesRGBColor(c, plotargs::KW, n::Int)
function getSeriesRGBColor(c, sp::Subplot, n::Int)
if c == :auto
c = autopick(plotargs[:color_palette], n)
c = autopick(sp[:color_palette], n)
end
# c should now be a subtype of ColorScheme
+261 -106
View File
@@ -1,47 +1,46 @@
export
P2,
P3,
BezierCurve,
curve_points,
directed_curve
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::Vector{Float64}
y::Vector{Float64}
function Shape(x::AVec, y::AVec)
if x[1] != x[end] || y[1] != y[end]
new(vcat(x, x[1]), vcat(y, y[1]))
else
new(x, y)
end
end
end
Shape(verts::AVec) = Shape(unzip(verts)...)
Shape(x, y) = Shape(collect(zip(x, y)))
get_xs(shape::Shape) = shape.x
get_ys(shape::Shape) = shape.y
vertices(shape::Shape) = collect(zip(shape.x, shape.y))
get_xs(shape::Shape) = Float64[v[1] for v in shape.vertices]
get_ys(shape::Shape) = Float64[v[2] for v in shape.vertices]
function scale(shape::Shape, x, y=x)
sx, sy = shape_coords(shape)
Shape(sx .* x, sy .* y)
end
function translate(shape::Shape, x, y=x)
sx, sy = shape_coords(shape)
Shape(sx .+ x, sy .+ y)
end
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 = unzip(shapes[1].vertices)
x, y = map(copy, shape_coords(shapes[1]))
for shape in shapes[2:end]
tmpx, tmpy = unzip(shape.vertices)
x = vcat(x, NaN, tmpx)
y = vcat(y, NaN, tmpy)
nanappend!(x, shape.x)
nanappend!(y, shape.y)
end
x, y
end
@@ -60,9 +59,6 @@ function weave(x,y; ordering = Vector[x,y])
try
push!(ret, shift!(o))
end
# try
# push!(ret, shift!(y))
# end
end
done = isempty(x) && isempty(y)
end
@@ -96,8 +92,37 @@ function makecross(; offset = -0.5, radius = 1.0)
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 _shape_keys = Symbol[
:circle,
:rect,
:star5,
:diamond,
:hexagon,
:cross,
:xcross,
:utriangle,
:dtriangle,
:pentagon,
:heptagon,
:octagon,
:star4,
:star6,
:star7,
:star8,
:vline,
:hline,
]
const _shapes = KW(
:ellipse => makeshape(20),
:circle => makeshape(20),
:rect => makeshape(4, offset=-0.25),
:diamond => makeshape(4),
:utriangle => makeshape(3),
@@ -113,7 +138,86 @@ const _shapes = KW(
)
for n in [4,5,6,7,8]
_shapes[symbol("star$n")] = makestar(n)
_shapes[Symbol("star$n")] = makestar(n)
end
# -----------------------------------------------------------------------
# uses the centroid calculation from https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
function center(shape::Shape)
x, y = shape_coords(shape)
n = length(x)
A, Cx, Cy = 0.0, 0.0, 0.0
for i=1:n
ip1 = i==n ? 1 : i+1
A += x[i] * y[ip1] - x[ip1] * y[i]
end
A *= 0.5
for i=1:n
ip1 = i==n ? 1 : i+1
m = (x[i] * y[ip1] - x[ip1] * y[i])
Cx += (x[i] + x[ip1]) * m
Cy += (y[i] + y[ip1]) * m
end
Cx / 6A, Cy / 6A
end
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
# -----------------------------------------------------------------------
@@ -142,7 +246,14 @@ function font(args...)
for arg in args
T = typeof(arg)
if arg == :center
if T == Font
family = arg.family
pointsize = arg.pointsize
halign = arg.halign
valign = arg.valign
rotation = arg.rotation
color = arg.color
elseif arg == :center
halign = :hcenter
valign = :vcenter
elseif arg in (:hcenter, :left, :right)
@@ -151,7 +262,7 @@ function font(args...)
valign = arg
elseif T <: Colorant
color = arg
elseif T <: @compat Union{Symbol,AbstractString}
elseif T <: Symbol || T <: AbstractString
try
color = parse(Colorant, string(arg))
catch
@@ -171,15 +282,25 @@ end
"Wrap a string with font info"
immutable PlotText
str::@compat(AbstractString)
str::AbstractString
font::Font
end
PlotText(str) = PlotText(string(str), font())
text(t::PlotText) = t
text(str::AbstractString, f::Font) = PlotText(str, f)
function text(str, args...)
PlotText(string(str), font(args...))
end
annotations(::Void) = []
annotations(anns::AVec) = anns
annotations(anns) = Any[anns]
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
immutable Stroke
@@ -190,10 +311,6 @@ immutable Stroke
end
function stroke(args...; alpha = nothing)
# defaults
# width = 1
# color = colorant"black"
# style = :solid
width = nothing
color = nothing
style = nothing
@@ -206,14 +323,12 @@ function stroke(args...; alpha = nothing)
style = arg
elseif T <: Colorant
color = arg
elseif T <: @compat Union{Symbol,AbstractString}
elseif T <: Symbol || T <: 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
@@ -232,9 +347,6 @@ immutable Brush
end
function brush(args...; alpha = nothing)
# defaults
# sz = 1
# color = colorant"black"
size = nothing
color = nothing
@@ -243,14 +355,12 @@ function brush(args...; alpha = nothing)
if T <: Colorant
color = arg
elseif T <: @compat Union{Symbol,AbstractString}
elseif T <: Symbol || T <: 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
@@ -279,12 +389,10 @@ 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])
Surface(f::Function, x, y) = Surface(Float64[f(xi,yi) for yi in y, xi in x])
Base.Array(surf::Surface) = surf.surf
@@ -292,7 +400,15 @@ for f in (:length, :size)
@eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...)
end
Base.copy(surf::Surface) = Surface(copy(surf.surf))
Base.eltype(surf::Surface) = eltype(surf.surf)
function expand_extrema!(a::Axis, surf::Surface)
ex = a[:extrema]
for vi in surf.surf
expand_extrema!(ex, vi)
end
ex
end
"For the case of representing a surface as a function of x/y... can possibly avoid allocations."
immutable SurfaceFunction <: AbstractSurface
@@ -301,67 +417,106 @@ end
# -----------------------------------------------------------------------
type OHLC{T<:Real}
open::T
high::T
low::T
close::T
# style is :open or :closed (for now)
immutable Arrow
style::Symbol
headlength::Float64
headwidth::Float64
end
function arrow(args...)
style = :simple
headlength = 0.3
headwidth = 0.3
setlength = false
for arg in args
T = typeof(arg)
if T == Symbol
style = arg
elseif T <: Number
# first we apply to both, but if there's more, then only change width after the first number
headwidth = Float64(arg)
if !setlength
headlength = headwidth
end
setlength = true
elseif T <: Tuple && length(arg) == 2
headlength, headwidth = Float64(arg[1]), Float64(arg[2])
else
warn("Skipped arrow arg $arg")
end
end
Arrow(style, headlength, headwidth)
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
[]
# allow for do-block notation which gets called on every valid start/end pair which
# we need to draw an arrow
function add_arrows(func::Function, x::AVec, y::AVec)
for i=2:length(x)
xyprev = (x[i-1], y[i-1])
xy = (x[i], y[i])
if ok(xyprev) && ok(xy)
if i==length(x) || !ok(x[i+1], y[i+1])
# add the arrow from xyprev to xy
func(xyprev, xy)
end
end
end
end
BezierCurve([p, upper_control, inside_control_points..., lower_control, q])
# -----------------------------------------------------------------------
type BezierCurve{T <: FixedSizeArrays.Vec}
control_points::Vector{T}
end
@compat function (bc::BezierCurve)(t::Real)
p = zero(P2)
n = length(bc.control_points)-1
for i in 0:n
p += bc.control_points[i+1] * binomial(n, i) * (1-t)^(n-i) * t^i
end
p
end
# 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
+349
View File
@@ -0,0 +1,349 @@
"""
Holds all data needed for a documentation example... header, description, and plotting expression (Expr)
"""
type PlotExample
header::AbstractString
desc::AbstractString
exprs::Vector{Expr}
end
# the _examples we'll run for each
const _examples = PlotExample[
PlotExample("Lines",
"A simple line plot of the columns.",
[:(begin
plot(Plots.fakedata(50,5), w=3)
end)]
),
PlotExample("Functions, adding data, and animations",
"Plot multiple functions. You can also put the function first, or use the form `plot(f, xmin, xmax)` where f is a Function or AbstractVector{Function}.\n\nGet series data: `x, y = plt[i]`. Set series data: `plt[i] = (x,y)`. Add to the series with `push!`/`append!`.\n\nEasily build animations. (`convert` or `ffmpeg` must be available to generate the animation.) Use command `gif(anim, filename, fps=15)` to save the animation.",
[:(begin
p = plot([sin,cos], zeros(0), leg=false)
anim = Animation()
for x in linspace(0, 10π, 100)
push!(p, x, Float64[sin(x), cos(x)])
frame(anim)
end
end)]
),
PlotExample("Parametric plots",
"Plot function pair (x(u), y(u)).",
[:(begin
plot(sin, x->sin(2x), 0, 2π, line=4, leg=false, fill=(0,:orange))
end)]
),
PlotExample("Colors",
"Access predefined palettes (or build your own with the `colorscheme` method). Line/marker colors are auto-generated from the plot's palette, unless overridden. Set the `z` argument to turn on series gradients.",
[:(begin
y = rand(100)
plot(0:10:100,rand(11,4),lab="lines",w=3,palette=:grays,fill=(0,:auto), α=0.6)
scatter!(y, zcolor=abs(y-.5), m=(:heat,0.8,stroke(1,:green)), ms=10*abs(y-0.5)+4, lab="grad")
end)]
),
PlotExample("Global",
"Change the guides/background/limits/ticks. Convenience args `xaxis` and `yaxis` allow you to pass a tuple or value which will be mapped to the relevant args automatically. The `xaxis` below will be replaced with `xlabel` and `xlims` args automatically during the preprocessing step. You can also use shorthand functions: `title!`, `xaxis!`, `yaxis!`, `xlabel!`, `ylabel!`, `xlims!`, `ylims!`, `xticks!`, `yticks!`",
[:(begin
y = rand(20,3)
plot(y, xaxis=("XLABEL",(-5,30),0:2:20,:flip), background_color = RGB(0.2,0.2,0.2), leg=false)
hline!(mean(y,1)+rand(1,3), line=(4,:dash,0.6,[:lightgreen :green :darkgreen]))
vline!([5,10])
title!("TITLE")
yaxis!("YLABEL", :log10)
end)]
),
# PlotExample("Two-axis",
# "Use the `axis` arguments.",
# [
# :(plot(Vector[randn(100), randn(100)*100], axis = [:l :r], ylabel="LEFT", yrightlabel="RIGHT", xlabel="X", title="TITLE"))
# ]),
PlotExample("Images",
"Plot an image. y-axis is set to flipped",
[:(begin
import Images
img = Images.load(Pkg.dir("PlotReferenceImages","Plots","pyplot","0.7.0","ref1.png"))
plot(img)
end)]
),
PlotExample("Arguments",
"Plot multiple series with different numbers of points. Mix arguments that apply to all series (marker/markersize) with arguments unique to each series (colors). Special arguments `line`, `marker`, and `fill` will automatically figure out what arguments to set (for example, we are setting the `linestyle`, `linewidth`, and `color` arguments with `line`.) Note that we pass a matrix of colors, and this applies the colors to each series.",
[:(begin
ys = Vector[rand(10), rand(20)]
plot(ys, color=[:black :orange], line=(:dot,4), marker=([:hex :d],12,0.8,stroke(3,:gray)))
end)]
),
PlotExample("Build plot in pieces",
"Start with a base plot...",
[:(begin
plot(rand(100)/3, reg=true, fill=(0,:green))
end)]
),
PlotExample("",
"and add to it later.",
[:(begin
scatter!(rand(100), markersize=6, c=:orange)
end)]
),
PlotExample("Histogram2D",
"",
[:(begin
histogram2d(randn(10000), randn(10000), nbins=20)
end)]
),
PlotExample("Line types",
"",
[:(begin
linetypes = [:path :steppre :steppost :sticks :scatter]
n = length(linetypes)
x = Vector[sort(rand(20)) for i in 1:n]
y = rand(20,n)
plot(x, y, line=(linetypes,3), lab=map(string,linetypes), ms=15)
end)]
),
PlotExample("Line styles",
"",
[:(begin
styles = filter(s -> s in supported_styles(), [:solid, :dash, :dot, :dashdot, :dashdotdot])'
n = length(styles)
y = cumsum(randn(20,n),1)
plot(y, line = (5, styles), label = map(string,styles))
end)]
),
PlotExample("Marker types",
"",
[:(begin
markers = filter(m -> m in supported_markers(), Plots._shape_keys)'
n = length(markers)
x = linspace(0,10,n+2)[2:end-1]
y = repmat(reverse(x)', n, 1)
scatter(x, y, m=(8,:auto), lab=map(string,markers), bg=:linen, xlim=(0,10), ylim=(0,10))
end)]
),
PlotExample("Bar",
"x is the midpoint of the bar. (todo: allow passing of edges instead of midpoints)",
[:(begin
bar(randn(99))
end)]
),
PlotExample("Histogram",
"",
[:(begin
histogram(randn(1000), nbins=20)
end)]
),
PlotExample("Subplots",
"""
Use the `layout` keyword, and optionally the convenient `@layout` macro to generate arbitrarily complex subplot layouts.
""",
[:(begin
l = @layout([a{0.1h}; b [c;d e]])
plot(randn(100,5), layout=l, t=[:line :histogram :scatter :steppre :bar], leg=false, ticks=nothing, border=false)
end)]
),
PlotExample("Adding to subplots",
"Note here the automatic grid layout, as well as the order in which new series are added to the plots.",
[:(begin
plot(Plots.fakedata(100,10), layout=4, palette=[:grays :blues :heat :lightrainbow], bg_inside=[:orange :pink :darkblue :black])
end)]
),
PlotExample("",
"",
[:(begin
srand(111)
plot!(Plots.fakedata(100,10))
end)]
),
PlotExample("Open/High/Low/Close",
"Create an OHLC chart. Pass in a list of (open,high,low,close) tuples as your `y` argument. This uses recipes to first convert the tuples to OHLC objects, and subsequently create a :path series with the appropriate line segments.",
[:(begin
n=20
hgt=rand(n)+1
bot=randn(n)
openpct=rand(n)
closepct=rand(n)
y = OHLC[(openpct[i]*hgt[i]+bot[i], bot[i]+hgt[i], bot[i], closepct[i]*hgt[i]+bot[i]) for i in 1:n]
ohlc(y)
end)]
),
PlotExample("Annotations",
"The `annotations` keyword is used for text annotations in data-coordinates. Pass in a tuple (x,y,text) or a vector of annotations. `annotate!(ann)` is shorthand for `plot!(; annotation=ann)`. Series annotations are used for annotating individual data points. They require only the annotation... x/y values are computed. A `PlotText` object can be build with the method `text(string, attr...)`, which wraps font and color attributes.",
[:(begin
y = rand(10)
plot(y, annotations = (3,y[3],text("this is #3",:left)), leg=false)
annotate!([(5, y[5], text("this is #5",16,:red,:center)), (10, y[10], text("this is #10",:right,20,"courier"))])
scatter!(linspace(2,8,6), rand(6), marker=(50,0.2,:orange), series_annotations = ["series","annotations","map","to","series",text("data",:green)])
end)]
),
PlotExample("Custom Markers",
"A `Plots.Shape` is a light wrapper around vertices of a polygon. For supported backends, pass arbitrary polygons as the marker shapes. Note: The center is (0,0) and the size is expected to be rougly the area of the unit circle.",
[:(begin
verts = [(-1.0,1.0),(-1.28,0.6),(-0.2,-1.4),(0.2,-1.4),(1.28,0.6),(1.0,1.0),
(-1.0,1.0),(-0.2,-0.6),(0.0,-0.2),(-0.4,0.6),(1.28,0.6),(0.2,-1.4),
(-0.2,-1.4),(0.6,0.2),(-0.2,0.2),(0.0,-0.2),(0.2,0.2),(-0.2,-0.6)]
x = 0.1:0.2:0.9
y = 0.7rand(5)+0.15
plot(x, y, line = (3,:dash,:lightblue), marker = (Shape(verts),30,RGBA(0,0,0,0.2)),
bg=:pink, fg=:darkblue, xlim = (0,1), ylim=(0,1), leg=false)
end)]
),
PlotExample("Contours",
"Any value for fill works here. We first build a filled contour from a function, then an unfilled contour from a matrix.",
[:(begin
x = 1:0.5:20
y = 1:0.5:10
f(x,y) = (3x+y^2)*abs(sin(x)+cos(y))
X = repmat(x', length(y), 1)
Y = repmat(y, 1, length(x))
Z = map(f, X, Y)
p1 = contour(x, y, f, fill=true)
p2 = contour(x, y, Z)
plot(p1, p2)
end)]
# [:(begin
# x = 1:0.3:20
# y = x
# f(x,y) = sin(x)+cos(y)
# contour(x, y, f, fill=true)
# end)]
),
PlotExample("Pie",
"",
[:(begin
x = ["Nerds", "Hackers", "Scientists"]
y = [0.4, 0.35, 0.25]
pie(x, y, title="The Julia Community", l=0.5)
end)]
),
PlotExample("3D",
"",
[:(begin
n = 100
ts = linspace(0,8π,n)
x = ts .* map(cos,ts)
y = 0.1ts .* map(sin,ts)
z = 1:n
plot(x, y, z, zcolor=reverse(z), m=(10,0.8,:blues,stroke(0)), leg=false, cbar=true, w=5)
plot!(zeros(n),zeros(n),1:n, w=10)
end)]
),
PlotExample("DataFrames",
"Plot using DataFrame column symbols.",
[:(begin
import RDatasets
iris = RDatasets.dataset("datasets", "iris")
scatter(iris, :SepalLength, :SepalWidth, group=:Species,
title = "My awesome plot", xlabel = "Length", ylabel = "Width",
marker = (0.5, [:+ :h :star7], 12), bg=RGB(.2,.2,.2))
end)]
),
PlotExample("Groups and Subplots",
"",
[:(begin
group = rand(map(i->"group $i",1:4),100)
plot(rand(100), layout=@layout([a b;c]), group=group, linetype=[:bar :scatter :steppre])
end)]
),
PlotExample("Polar Plots",
"",
[:(begin
Θ = linspace(0,1.5π,100)
r = abs(0.1randn(100)+sin(3Θ))
plot(Θ, r, proj=:polar, m=2)
end)]
),
PlotExample("Heatmap, categorical axes, and aspect_ratio",
"",
[:(begin
xs = [string("x",i) for i=1:10]
ys = [string("y",i) for i=1:4]
z = float((1:4)*(1:10)')
heatmap(xs, ys, z, aspect_ratio=1)
end)]
),
PlotExample("Layouts, margins, label rotation, title location",
"",
[:(begin
plot(rand(100,6),layout=@layout([a b; c]),title=["A" "B" "C"],
title_location=:left, left_margin=[20mm 0mm],
bottom_margin=50px, xrotation=60)
end)]
),
PlotExample("Boxplot and Violin series recipes",
"",
[:(begin
import RDatasets
singers = RDatasets.dataset("lattice", "singer")
violin(singers, :VoicePart, :Height, marker = (0.2, :blue, stroke(0)))
boxplot!(singers, :VoicePart, :Height, marker = (0.3, :orange, stroke(2)))
end)]
)
]
# ---------------------------------------------------------------------------------
# make and display one plot
function test_examples(pkgname::Symbol, idx::Int; debug = false, disp = true)
Plots._debugMode.on = debug
info("Testing plot: $pkgname:$idx:$(_examples[idx].header)")
backend(pkgname)
backend()
map(eval, _examples[idx].exprs)
plt = current()
if disp
gui(plt)
end
plt
end
# generate all plots and create a dict mapping idx --> plt
function test_examples(pkgname::Symbol; debug = false, disp = true, sleep = nothing,
skip = [], only = nothing)
Plots._debugMode.on = debug
plts = Dict()
for i in 1:length(_examples)
only != nothing && !(i in only) && continue
i in skip && continue
try
plt = test_examples(pkgname, i, debug=debug, disp=disp)
plts[i] = plt
catch ex
# TODO: put error info into markdown?
warn("Example $pkgname:$i:$(_examples[i].header) failed with: $ex")
end
if sleep != nothing
Base.sleep(sleep)
end
end
plts
end
+641 -147
View File
@@ -1,178 +1,672 @@
# NOTE: (0,0) is the top-left !!!
# allow pixels and percentages
const px = AbsoluteLength(0.254)
const pct = Length{:pct, Float64}(1.0)
to_pixels(m::AbsoluteLength) = m.value / 0.254
const _cbar_width = 5mm
@compat Base.:.*(m::Measure, n::Number) = m * n
@compat Base.:.*(n::Number, m::Measure) = m * n
@compat Base.:-(m::Measure, a::AbstractArray) = map(ai -> m - ai, a)
@compat Base.:-(a::AbstractArray, m::Measure) = map(ai -> ai - m, a)
Base.zero(::Type{typeof(mm)}) = 0mm
Base.one(::Type{typeof(mm)}) = 1mm
Base.typemin(::typeof(mm)) = -Inf*mm
Base.typemax(::typeof(mm)) = Inf*mm
Base.convert{F<:AbstractFloat}(::Type{F}, l::AbsoluteLength) = convert(F, l.value)
# TODO: these are unintuitive and may cause tricky bugs
# @compat Base.:+(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value))
# @compat Base.:+(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (1 + m1.value))
# @compat Base.:-(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 - m2.value))
# @compat Base.:-(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (m1.value - 1))
@compat Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value)
@compat Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value)
@compat Base.:/(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value)
@compat Base.:/(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value)
Base.zero(::Type{typeof(pct)}) = 0pct
Base.one(::Type{typeof(pct)}) = 1pct
Base.typemin(::typeof(pct)) = 0pct
Base.typemax(::typeof(pct)) = 1pct
const defaultbox = BoundingBox(0mm, 0mm, 0mm, 0mm)
left(bbox::BoundingBox) = bbox.x0[1]
top(bbox::BoundingBox) = bbox.x0[2]
right(bbox::BoundingBox) = left(bbox) + width(bbox)
bottom(bbox::BoundingBox) = top(bbox) + height(bbox)
Base.size(bbox::BoundingBox) = (width(bbox), height(bbox))
# @compat Base.:*{T,N}(m1::Length{T,N}, m2::Length{T,N}) = Length{T,N}(m1.value * m2.value)
ispositive(m::Measure) = m.value > 0
# union together bounding boxes
@compat function Base.:+(bb1::BoundingBox, bb2::BoundingBox)
# empty boxes don't change the union
ispositive(width(bb1)) || return bb2
ispositive(height(bb1)) || return bb2
ispositive(width(bb2)) || return bb1
ispositive(height(bb2)) || return bb1
l = min(left(bb1), left(bb2))
t = min(top(bb1), top(bb2))
r = max(right(bb1), right(bb2))
b = max(bottom(bb1), bottom(bb2))
BoundingBox(l, t, r-l, b-t)
end
# this creates a bounding box in the parent's scope, where the child bounding box
# is relative to the parent
function crop(parent::BoundingBox, child::BoundingBox)
l = left(parent) + left(child)
t = top(parent) + top(child)
w = width(child)
h = height(child)
BoundingBox(l, t, w, h)
end
# convert x,y coordinates from absolute coords to percentages...
# returns x_pct, y_pct
function xy_mm_to_pcts(x::AbsoluteLength, y::AbsoluteLength, figw, figh, flipy = true)
xmm, ymm = x.value, y.value
if flipy
ymm = figh.value - ymm # flip y when origin in bottom-left
end
xmm / figw.value, ymm / figh.value
end
# convert a bounding box from absolute coords to percentages...
# returns an array of percentages of figure size: [left, bottom, width, height]
function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true)
mms = Float64[f(bb).value for f in (left,bottom,width,height)]
if flipy
mms[2] = figh.value - mms[2] # flip y when origin in bottom-left
end
mms ./ Float64[figw.value, figh.value, figw.value, figh.value]
end
function Base.show(io::IO, bbox::BoundingBox)
print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}")
end
# -----------------------------------------------------------
# AbstractLayout
Base.show(io::IO, layout::AbstractLayout) = print(io, "$(typeof(layout))$(size(layout))")
# create a new bbox
function bbox(x, y, w, h; h_anchor = :left, v_anchor = :top)
left = if h_anchor == :left
x
else
x - w * (h_anchor == :right ? 1.0 : 0.5)
end
top = if v_anchor == :top
y
else
y - h * (v_anchor == :bottom ? 1.0 : 0.5)
end
BoundingBox(left, top, w, h)
end
# this is the available area for drawing everything in this layout... as percentages of total canvas
bbox(layout::AbstractLayout) = layout.bbox
bbox!(layout::AbstractLayout, bb::BoundingBox) = (layout.bbox = bb)
# layouts are recursive, tree-like structures, and most will have a parent field
Base.parent(layout::AbstractLayout) = layout.parent
parent_bbox(layout::AbstractLayout) = bbox(parent(layout))
# padding_w(layout::AbstractLayout) = left_padding(layout) + right_padding(layout)
# padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout)
# padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout))
update_position!(layout::AbstractLayout) = nothing
update_child_bboxes!(layout::AbstractLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) = nothing
left(layout::AbstractLayout) = left(bbox(layout))
top(layout::AbstractLayout) = top(bbox(layout))
right(layout::AbstractLayout) = right(bbox(layout))
bottom(layout::AbstractLayout) = bottom(bbox(layout))
width(layout::AbstractLayout) = width(bbox(layout))
height(layout::AbstractLayout) = height(bbox(layout))
# pass these through to the bbox methods if there's no plotarea
plotarea(layout::AbstractLayout) = bbox(layout)
plotarea!(layout::AbstractLayout, bb::BoundingBox) = bbox!(layout, bb)
attr(layout::AbstractLayout, k::Symbol) = layout.attr[k]
attr(layout::AbstractLayout, k::Symbol, v) = get(layout.attr, k, v)
attr!(layout::AbstractLayout, v, k::Symbol) = (layout.attr[k] = v)
hasattr(layout::AbstractLayout, k::Symbol) = haskey(layout.attr, k)
leftpad(layout::AbstractLayout) = 0mm
toppad(layout::AbstractLayout) = 0mm
rightpad(layout::AbstractLayout) = 0mm
bottompad(layout::AbstractLayout) = 0mm
# -----------------------------------------------------------
# RootLayout
# this is the parent of the top-level layout
immutable RootLayout <: AbstractLayout end
Base.parent(::RootLayout) = nothing
parent_bbox(::RootLayout) = defaultbox
bbox(::RootLayout) = defaultbox
# -----------------------------------------------------------
# EmptyLayout
# contains blank space
type EmptyLayout <: AbstractLayout
parent::AbstractLayout
bbox::BoundingBox
attr::KW # store label, width, and height for initialization
# label # this is the label that the subplot will take (since we create a layout before initialization)
end
EmptyLayout(parent = RootLayout(); kw...) = EmptyLayout(parent, defaultbox, KW(kw))
Base.size(layout::EmptyLayout) = (0,0)
Base.length(layout::EmptyLayout) = 0
Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing
_update_min_padding!(layout::EmptyLayout) = nothing
# -----------------------------------------------------------
# GridLayout
# -----------------------------------------------------------
"Simple grid, indices are row-major."
immutable GridLayout <: SubplotLayout
nr::Int
nc::Int
# nested, gridded layout with optional size percentages
type GridLayout <: AbstractLayout
parent::AbstractLayout
minpad::Tuple # leftpad, toppad, rightpad, bottompad
bbox::BoundingBox
grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion
widths::Vector{Measure}
heights::Vector{Measure}
attr::KW
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
grid(args...; kw...) = GridLayout(args...; kw...)
function GridLayout(dims...;
parent = RootLayout(),
widths = zeros(dims[2]),
heights = zeros(dims[1]),
kw...)
grid = Matrix{AbstractLayout}(dims...)
layout = GridLayout(
parent,
(20mm, 5mm, 2mm, 10mm),
defaultbox,
grid,
Measure[w*pct for w in widths],
Measure[h*pct for h in heights],
# convert(Vector{Float64}, widths),
# convert(Vector{Float64}, heights),
KW(kw))
fill!(grid, EmptyLayout(layout))
layout
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}
Base.size(layout::GridLayout) = size(layout.grid)
Base.length(layout::GridLayout) = length(layout.grid)
Base.getindex(layout::GridLayout, r::Int, c::Int) = layout.grid[r,c]
function Base.setindex!(layout::GridLayout, v, r::Int, c::Int)
layout.grid[r,c] = v
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
leftpad(layout::GridLayout) = layout.minpad[1]
toppad(layout::GridLayout) = layout.minpad[2]
rightpad(layout::GridLayout) = layout.minpad[3]
bottompad(layout::GridLayout) = layout.minpad[4]
# here's how this works... first we recursively "update the minimum padding" (which
# means to calculate the minimum size needed from the edge of the subplot to plot area)
# for the whole layout tree. then we can compute the "padding borders" of this
# layout as the biggest padding of the children on the perimeter. then we need to
# recursively pass those borders back down the tree, one side at a time, but ONLY
# to those perimeter children.
# leftpad, toppad, rightpad, bottompad
function _update_min_padding!(layout::GridLayout)
map(_update_min_padding!, layout.grid)
layout.minpad = (
maximum(map(leftpad, layout.grid[:,1])),
maximum(map(toppad, layout.grid[1,:])),
maximum(map(rightpad, layout.grid[:,end])),
maximum(map(bottompad, layout.grid[end,:]))
)
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}
function update_position!(layout::GridLayout)
map(update_position!, layout.grid)
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
# some lengths are fixed... we have to split up the free space among the list v
function recompute_lengths(v)
# dump(v)
tot = 0pct
cnt = 0
for vi in v
if vi == 0pct
cnt += 1
else
error("How do we process? $v")
tot += vi
end
end
if widths == nothing
widths = ones(nc) ./ nc
end
if heights == nothing
heights = ones(nr) ./ nr
leftover = 1.0pct - tot
if cnt > 1 && leftover.value <= 0
error("Not enough length left over in layout! v = $v, cnt = $cnt, leftover = $leftover")
end
FlexLayout(n, grid, widths, heights)
# now fill in the blanks
Measure[(vi == 0pct ? leftover / cnt : vi) for vi in v]
end
# recursively compute the bounding boxes for the layout and plotarea (relative to canvas!)
function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0mm,0mm])
nr, nc = size(layout)
function subplotlayout(sz::Tuple{Int,Int})
GridLayout(sz...)
# # create a matrix for each minimum padding direction
# _update_min_padding!(layout)
minpad_left = map(leftpad, layout.grid)
minpad_top = map(toppad, layout.grid)
minpad_right = map(rightpad, layout.grid)
minpad_bottom = map(bottompad, layout.grid)
# @show minpad_left minpad_top minpad_right minpad_bottom
# get the max horizontal (left and right) padding over columns,
# and max vertical (bottom and top) padding over rows
# TODO: add extra padding here
pad_left = maximum(minpad_left, 1)
pad_top = maximum(minpad_top, 2)
pad_right = maximum(minpad_right, 1)
pad_bottom = maximum(minpad_bottom, 2)
# @show pad_left pad_top pad_right pad_bottom
# make sure the perimeter match the parent
pad_left[1] = max(pad_left[1], minimum_perimeter[1])
pad_top[1] = max(pad_top[1], minimum_perimeter[2])
pad_right[end] = max(pad_right[end], minimum_perimeter[3])
pad_bottom[end] = max(pad_bottom[end], minimum_perimeter[4])
# scale this up to the total padding in each direction
total_pad_horizontal = sum(pad_left + pad_right)
total_pad_vertical = sum(pad_top + pad_bottom)
# @show total_pad_horizontal total_pad_vertical
# now we can compute the total plot area in each direction
total_plotarea_horizontal = width(layout) - total_pad_horizontal
total_plotarea_vertical = height(layout) - total_pad_vertical
# @show total_plotarea_horizontal total_plotarea_vertical
# recompute widths/heights
layout.widths = recompute_lengths(layout.widths)
layout.heights = recompute_lengths(layout.heights)
# @show layout.widths layout.heights
# normalize widths/heights so they sum to 1
# denom_w = sum(layout.widths)
# denom_h = sum(layout.heights)
# @show layout.widths layout.heights denom_w, denom_h
# we have all the data we need... lets compute the plot areas and set the bounding boxes
for r=1:nr, c=1:nc
child = layout[r,c]
# get the top-left corner of this child... the first one is top-left of the parent (i.e. layout)
child_left = (c == 1 ? left(layout.bbox) : right(layout[r, c-1].bbox))
child_top = (r == 1 ? top(layout.bbox) : bottom(layout[r-1, c].bbox))
# compute plot area
plotarea_left = child_left + pad_left[c]
plotarea_top = child_top + pad_top[r]
plotarea_width = total_plotarea_horizontal * layout.widths[c]
plotarea_height = total_plotarea_vertical * layout.heights[r]
plotarea!(child, BoundingBox(plotarea_left, plotarea_top, plotarea_width, plotarea_height))
# compute child bbox
child_width = pad_left[c] + plotarea_width + pad_right[c]
child_height = pad_top[r] + plotarea_height + pad_bottom[r]
bbox!(child, BoundingBox(child_left, child_top, child_width, child_height))
# this is the minimum perimeter as decided by this child's parent, so that
# all children on this border have the same value
min_child_perimeter = [
c == 1 ? layout.minpad[1] : 0mm,
r == 1 ? layout.minpad[2] : 0mm,
c == nc ? layout.minpad[3] : 0mm,
r == nr ? layout.minpad[4] : 0mm
]
# recursively update the child's children
update_child_bboxes!(child, min_child_perimeter)
end
end
function subplotlayout(rowcounts::AVec{Int})
RowsLayout(sum(rowcounts), rowcounts)
# for each inset (floating) subplot, resolve the relative position
# to absolute canvas coordinates, relative to the parent's plotarea
function update_inset_bboxes!(plt::Plot)
for sp in plt.inset_subplots
p_area = Measures.resolve(plotarea(sp.parent), sp[:relative_bbox])
# @show bbox(sp.parent) sp[:relative_bbox] p_area
plotarea!(sp, p_area)
bbox!(sp, bbox(
left(p_area) - leftpad(sp),
top(p_area) - toppad(sp),
width(p_area) + leftpad(sp) + rightpad(sp),
height(p_area) + toppad(sp) + bottompad(sp)
))
end
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)
calc_num_subplots(layout::AbstractLayout) = 1
function calc_num_subplots(layout::GridLayout)
tot = 0
for l in layout.grid
tot += calc_num_subplots(l)
end
tot
end
function compute_gridsize(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
nr = ceil(Int, numplts / nc)
nc = ceil(Int, numplts / nr)
end
nr, nc
end
# ----------------------------------------------------------------------
# constructors
# pass the layout arg through
function layout_args(d::KW)
layout_args(get(d, :layout, default(:layout)))
end
function layout_args(d::KW, n_override::Integer)
layout, n = layout_args(get(d, :layout, n_override))
if n != n_override
error("When doing layout, n != n_override. You're probably trying to force existing plots into a layout that doesn't fit them.")
end
layout, n
end
function layout_args(n::Integer)
nr, nc = compute_gridsize(n, -1, -1)
GridLayout(nr, nc), n
end
function layout_args{I<:Integer}(sztup::NTuple{2,I})
nr, nc = sztup
GridLayout(nr, nc), nr*nc
end
function layout_args{I<:Integer}(sztup::NTuple{3,I})
n, nr, nc = sztup
nr, nc = compute_gridsize(n, nr, nc)
GridLayout(nr, nc), n
end
# compute number of subplots
function layout_args(layout::GridLayout)
# recursively get the size of the grid
n = calc_num_subplots(layout)
layout, n
end
layout_args(huh) = error("unhandled layout type $(typeof(huh)): $huh")
# ----------------------------------------------------------------------
function build_layout(args...)
layout, n = layout_args(args...)
build_layout(layout, n)
end
# # just a single subplot
# function build_layout(sp::Subplot, n::Integer)
# sp, Subplot[sp], SubplotMap(gensym() => sp)
# end
# n is the number of subplots... build a grid and initialize the inner subplots recursively
function build_layout(layout::GridLayout, n::Integer)
nr, nc = size(layout)
subplots = Subplot[]
spmap = SubplotMap()
i = 0
for r=1:nr, c=1:nc
l = layout[r,c]
if isa(l, EmptyLayout) && !get(l.attr, :blank, false)
sp = Subplot(backend(), parent=layout)
layout[r,c] = sp
push!(subplots, sp)
spmap[attr(l,:label,gensym())] = sp
if get(l.attr, :width, :auto) != :auto
layout.widths[c] = attr(l,:width)
end
if get(l.attr, :height, :auto) != :auto
layout.heights[r] = attr(l,:height)
end
i += 1
elseif isa(l, GridLayout)
# sub-grid
l, sps, m = build_layout(l, n-i)
append!(subplots, sps)
merge!(spmap, m)
i += length(sps)
end
i >= n && break # only add n subplots
end
layout, subplots, spmap
end
# build a layout from a list of existing Plot objects
# TODO... much of the logic overlaps with the method above... can we merge?
function build_layout(layout::GridLayout, numsp::Integer, plts::AVec{Plot})
nr, nc = size(layout)
subplots = Subplot[]
spmap = SubplotMap()
i = 0
for r=1:nr, c=1:nc
l = layout[r,c]
if isa(l, EmptyLayout) && !get(l.attr, :blank, false)
plt = shift!(plts) # grab the first plot out of the list
layout[r,c] = plt.layout
append!(subplots, plt.subplots)
merge!(spmap, plt.spmap)
if get(l.attr, :width, :auto) != :auto
layout.widths[c] = attr(l,:width)
end
if get(l.attr, :height, :auto) != :auto
layout.heights[r] = attr(l,:height)
end
i += length(plt.subplots)
elseif isa(l, GridLayout)
# sub-grid
l, sps, m = build_layout(l, numsp-i, plts)
append!(subplots, sps)
merge!(spmap, m)
i += length(sps)
end
i >= numsp && break # only add n subplots
end
layout, subplots, spmap
end
# ----------------------------------------------------------------------
# @layout macro
function add_layout_pct!(kw::KW, v::Expr, idx::Integer, nidx::Integer)
# dump(v)
# something like {0.2w}?
if v.head == :call && v.args[1] == :*
num = v.args[2]
if length(v.args) == 3 && isa(num, Number)
units = v.args[3]
if units == :h
return kw[:h] = num*pct
elseif units == :w
return kw[:w] = num*pct
elseif units in (:pct, :px, :mm, :cm, :inch)
idx == 1 && (kw[:w] = v)
(idx == 2 || nidx == 1) && (kw[:h] = v)
# return kw[idx == 1 ? :w : :h] = v
end
end
end
error("Couldn't match layout curly (idx=$idx): $v")
end
function add_layout_pct!(kw::KW, v::Number, idx::Integer)
# kw[idx == 1 ? :w : :h] = v*pct
idx == 1 && (kw[:w] = v*pct)
(idx == 2 || nidx == 1) && (kw[:h] = v*pct)
end
isrow(v) = isa(v, Expr) && v.head in (:hcat,:row)
iscol(v) = isa(v, Expr) && v.head == :vcat
rowsize(v) = isrow(v) ? length(v.args) : 1
function create_grid(expr::Expr)
if iscol(expr)
create_grid_vcat(expr)
elseif isrow(expr)
:(let cell = GridLayout(1, $(length(expr.args)))
$([:(cell[1,$i] = $(create_grid(v))) for (i,v) in enumerate(expr.args)]...)
cell
end)
elseif expr.head == :curly
create_grid_curly(expr)
else
# if it's something else, just return that (might be an existing layout?)
expr
end
end
function create_grid_vcat(expr::Expr)
rowsizes = map(rowsize, expr.args)
rmin, rmax = extrema(rowsizes)
if rmin > 0 && rmin == rmax
# we have a grid... build the whole thing
# note: rmin is the number of columns
nr = length(expr.args)
nc = rmin
body = Expr(:block)
for r=1:nr
arg = expr.args[r]
if isrow(arg)
for (c,item) in enumerate(arg.args)
push!(body.args, :(cell[$r,$c] = $(create_grid(item))))
end
else
push!(body.args, :(cell[$r,1] = $(create_grid(arg))))
end
end
:(let cell = GridLayout($nr, $nc)
$body
cell
end)
else
# otherwise just build one row at a time
:(let cell = GridLayout($(length(expr.args)), 1)
$([:(cell[$i,1] = $(create_grid(v))) for (i,v) in enumerate(expr.args)]...)
cell
end)
end
end
function create_grid_curly(expr::Expr)
s = expr.args[1]
kw = KW()
for (i,arg) in enumerate(expr.args[2:end])
add_layout_pct!(kw, arg, i, length(expr.args)-1)
end
# @show kw
:(EmptyLayout(label = $(QuoteNode(s)), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto)))))
end
function create_grid(s::Symbol)
:(EmptyLayout(label = $(QuoteNode(s)), blank = $(s == :_)))
end
macro layout(mat::Expr)
create_grid(mat)
end
# -------------------------------------------------------------------------
# make all reference the same axis extrema/values
function link_axes!(axes::Axis...)
a1 = axes[1]
for i=2:length(axes)
a2 = axes[i]
for k in (:extrema, :discrete_values, :continuous_values, :discrete_map)
a2[k] = a1[k]
end
end
end
# for some vector or matrix of layouts, filter only the Subplots and link those axes
function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol)
subplots = filter(l -> isa(l, Subplot), a)
axes = [sp.attr[axissym] for sp in subplots]
if length(axes) > 0
link_axes!(axes...)
end
end
# don't do anything for most layout types
function link_axes!(l::AbstractLayout, link::Symbol)
end
# process a GridLayout, recursively linking axes according to the link symbol
function link_axes!(layout::GridLayout, link::Symbol)
nr, nc = size(layout)
if link in (:x, :both)
for c=1:nc
link_axes!(layout.grid[:,c], :xaxis)
end
end
if link in (:y, :both)
for r=1:nr
link_axes!(layout.grid[r,:], :yaxis)
end
end
if link == :all
link_axes!(layout.grid, :xaxis)
link_axes!(layout.grid, :yaxis)
end
for l in layout.grid
link_axes!(l, link)
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
+138 -34
View File
@@ -1,49 +1,49 @@
defaultOutputFormat(plt::AbstractPlot) = "png"
defaultOutputFormat(plt::Plot) = "png"
function png(plt::AbstractPlot, fn::@compat(AbstractString))
function png(plt::Plot, fn::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)
png(fn::AbstractString) = png(current(), fn)
function svg(plt::AbstractPlot, fn::@compat(AbstractString))
function svg(plt::Plot, fn::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)
svg(fn::AbstractString) = svg(current(), fn)
function pdf(plt::AbstractPlot, fn::@compat(AbstractString))
function pdf(plt::Plot, fn::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)
pdf(fn::AbstractString) = pdf(current(), fn)
function ps(plt::AbstractPlot, fn::@compat(AbstractString))
function ps(plt::Plot, fn::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)
ps(fn::AbstractString) = ps(current(), fn)
function tex(plt::AbstractPlot, fn::@compat(AbstractString))
function tex(plt::Plot, fn::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)
tex(fn::AbstractString) = tex(current(), fn)
# ----------------------------------------------------------------
@@ -57,7 +57,7 @@ tex(fn::@compat(AbstractString)) = tex(current(), fn)
"tex" => tex,
)
function getExtension(fn::@compat(AbstractString))
function getExtension(fn::AbstractString)
pieces = split(fn, ".")
length(pieces) > 1 || error("Can't extract file extension: ", fn)
ext = pieces[end]
@@ -65,7 +65,7 @@ function getExtension(fn::@compat(AbstractString))
ext
end
function addExtension(fn::@compat(AbstractString), ext::@compat(AbstractString))
function addExtension(fn::AbstractString, ext::AbstractString)
try
oldext = getExtension(fn)
if oldext == ext
@@ -78,7 +78,7 @@ function addExtension(fn::@compat(AbstractString), ext::@compat(AbstractString))
end
end
function savefig(plt::AbstractPlot, fn::@compat(AbstractString))
function savefig(plt::Plot, fn::AbstractString)
# get the extension
local ext
@@ -96,28 +96,132 @@ function savefig(plt::AbstractPlot, fn::@compat(AbstractString))
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))
savefig(fn::AbstractString) = savefig(current(), fn)
# ---------------------------------------------------------
gui(plt::AbstractPlot = current()) = display(PlotsDisplay(), plt)
gui(plt::Plot = 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)
function Base.display(::PlotsDisplay, plt::Plot)
prepare_output(plt)
_display(plt)
end
# override the REPL display to open a gui window
Base.display(::Base.REPL.REPLDisplay, ::MIME"text/plain", plt::Plot) = gui(plt)
# ---------------------------------------------------------
const _mimeformats = Dict(
"application/eps" => "eps",
"image/eps" => "eps",
"application/pdf" => "pdf",
"image/png" => "png",
"application/postscript" => "ps",
"image/svg+xml" => "svg"
)
const _best_html_output_type = KW(
:pyplot => :png,
)
# a backup for html... passes to svg or png depending on the html_output_format arg
function Base.writemime(io::IO, ::MIME"text/html", plt::Plot)
output_type = Symbol(plt.attr[:html_output_format])
if output_type == :auto
output_type = get(_best_html_output_type, backend_name(plt.backend), :svg)
end
if output_type == :png
# info("writing png to html output")
print(io, "<img src=\"data:image/png;base64,", base64encode(writemime, MIME("image/png"), plt), "\" />")
elseif output_type == :svg
# info("writing svg to html output")
writemime(io, MIME("image/svg+xml"), plt)
else
error("only png or svg allowed. got: $output_type")
end
end
# for writing to io streams... first prepare, then callback
for mime in keys(_mimeformats)
@eval function Base.writemime(io::IO, m::MIME{Symbol($mime)}, plt::Plot)
prepare_output(plt)
_writemime(io, m, plt)
end
end
# ---------------------------------------------------------
# A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert
if is_installed("FileIO")
@eval begin
import FileIO
function _writemime(io::IO, ::MIME"image/png", plt::Plot)
fn = tempname()
# first save a pdf file
pdf(plt, fn)
# load that pdf into a FileIO Stream
s = FileIO.load(fn * ".pdf")
# save a png
pngfn = fn * ".png"
FileIO.save(pngfn, s)
# now write from the file
write(io, readall(open(pngfn)))
end
end
end
# function html_output_format(fmt)
# if fmt == "png"
# @eval function Base.writemime(io::IO, ::MIME"text/html", plt::Plot)
# print(io, "<img src=\"data:image/png;base64,", base64(writemime, MIME("image/png"), plt), "\" />")
# end
# elseif fmt == "svg"
# @eval function Base.writemime(io::IO, ::MIME"text/html", plt::Plot)
# writemime(io, MIME("image/svg+xml"), plt)
# end
# else
# error("only png or svg allowed. got: $fmt")
# end
# end
#
# html_output_format("svg")
# ---------------------------------------------------------
# IJulia
# ---------------------------------------------------------
const _ijulia_output = Compat.ASCIIString["text/html"]
function setup_ijulia()
# override IJulia inline display
if isijulia()
@eval begin
import IJulia
export set_ijulia_output
function set_ijulia_output(mimestr::AbstractString)
# info("Setting IJulia output format to $mimestr")
global _ijulia_output
_ijulia_output[1] = mimestr
end
function IJulia.display_dict(plt::Plot)
global _ijulia_output
Dict{Compat.ASCIIString, ByteString}(_ijulia_output[1] => sprint(writemime, _ijulia_output[1], plt))
end
end
set_ijulia_output("text/html")
end
end
# ---------------------------------------------------------
# Atom PlotPane
@@ -125,21 +229,21 @@ end
function setup_atom()
# @require Atom begin
if isatom()
if isatom() && get(ENV, "PLOTS_USE_ATOM_PLOTPANE", false) in (true, 1, "1", "true", "yes")
# @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)
Atom.Media.media(Plot{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)))"
# Atom.displaysize(::Plot) = (535, 379)
# Atom.displaytitle(plt::Plot) = "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)
function Atom.Media.render(pane::Atom.PlotPane, plt::Plot)
Atom.Media.render(pane, Atom.div(Atom.d(), Atom.HTML(stringmime(MIME("text/html"), plt))))
end
+486 -191
View File
@@ -22,7 +22,7 @@ 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
getattr(plt::Plot, idx::Int = 1) = plt.attr
convertSeriesIndex(plt::Plot, n::Int) = n
# ---------------------------------------------------------
@@ -43,17 +43,93 @@ When you pass in matrices, it splits by columns. See the documentation for more
# this creates a new plot with args/kw and sets it to be the current plot
function plot(args...; kw...)
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
# create an empty Plot then process
plt = Plot()
# plt.user_attr = d
_plot!(plt, d, args...)
end
delete!(d, :background_color)
plot!(plt, args...; d...) # add to it
# build a new plot from existing plots
# note: we split into plt1 and plts_tail so we can dispatch correctly
function plot(plt1::Plot, plts_tail::Plot...; kw...)
d = KW(kw)
preprocessArgs!(d)
# build our plot vector from the args
n = length(plts_tail) + 1
plts = Array(Plot, n)
plts[1] = plt1
for (i,plt) in enumerate(plts_tail)
plts[i+1] = plt
end
# compute the layout
layout = layout_args(d, n)[1]
num_sp = sum([length(p.subplots) for p in plts])
# create a new plot object, with subplot list/map made of existing subplots.
# note: we create a new backend figure for this new plot object
# note: all subplots and series "belong" to this new plot...
plt = Plot()
# TODO: build the user_attr dict by creating "Any matrices" for the args of each subplot
# TODO: replace this with proper processing from a merged user_attr KW
# update plot args, first with existing plots, then override with d
for p in plts
_update_plot_args(plt, p.attr)
plt.n += p.n
end
_update_plot_args(plt, d)
# pass new plot to the backend
plt.o = _create_backend_figure(plt)
plt.init = true
# create the layout and initialize the subplots
plt.layout, plt.subplots, plt.spmap = build_layout(layout, num_sp, copy(plts))
for (idx, sp) in enumerate(plt.subplots)
_initialize_subplot(plt, sp)
serieslist = series_list(sp)
if sp in sp.plt.inset_subplots
push!(plt.inset_subplots, sp)
end
sp.plt = plt
sp.attr[:subplot_index] = idx
for series in serieslist
push!(plt.series_list, series)
_series_added(plt, series)
end
end
# # just in case the backend needs to set up the plot (make it current or something)
# _prepare_plot_object(plt)
# first apply any args for the subplots
for (idx,sp) in enumerate(plt.subplots)
_update_subplot_args(plt, sp, d, idx, remove_pair = false)
end
# # now we can get rid of the axis keys without a letter
# for k in keys(_axis_defaults)
# delete!(d, k)
# for letter in (:x,:y,:z)
# delete!(d, Symbol(letter,k))
# end
# end
# do we need to link any axes together?
link_axes!(plt.layout, plt[:link])
# finish up
current(plt)
if get(d, :show, default(:show))
gui()
end
plt
end
@@ -73,229 +149,448 @@ end
function plot!(plt::Plot, args...; kw...)
d = KW(kw)
preprocessArgs!(d)
# merge!(plt.user_attr, d)
_plot!(plt, d, args...)
end
function strip_first_letter(s::Symbol)
str = string(s)
str[1:1], Symbol(str[2:end])
end
# this method recursively applies series recipes when the seriestype is not supported
# natively by the backend
function _apply_series_recipe(plt::Plot, d::KW)
st = d[:seriestype]
# @show st
if st in supported_types()
# getting ready to add the series... last update to subplot from anything
# that might have been added during series recipes
sp = d[:subplot]
sp_idx = get_subplot_index(plt, sp)
_update_subplot_args(plt, sp, d, sp_idx)
# change to a 3d projection for this subplot?
if is3d(st)
sp.attr[:projection] = "3d"
end
# initialize now that we know the first series type
if !haskey(sp.attr, :init)
_initialize_subplot(plt, sp)
sp.attr[:init] = true
end
# adjust extrema and discrete info
if st == :image
w, h = size(d[:z])
expand_extrema!(sp[:xaxis], (0,w))
expand_extrema!(sp[:yaxis], (0,h))
sp[:yaxis].d[:flip] = true
elseif !(st in (:pie, :histogram, :histogram2d))
expand_extrema!(sp, d)
end
# add the series!
warnOnUnsupported_args(plt.backend, d)
warnOnUnsupported(plt.backend, d)
series = Series(d)
push!(plt.series_list, series)
# @show series
_series_added(plt, series)
else
# get a sub list of series for this seriestype
datalist = try
RecipesBase.apply_recipe(d, Val{st}, d[:x], d[:y], d[:z])
catch
warn("Exception during apply_recipe(Val{$st}, ...) with types ($(typeof(d[:x])), $(typeof(d[:y])), $(typeof(d[:z])))")
rethrow()
end
# assuming there was no error, recursively apply the series recipes
for data in datalist
if isa(data, RecipeData)
_apply_series_recipe(plt, data.d)
else
warn("Unhandled recipe: $(data)")
break
end
end
end
end
# this is the core plotting function. recursively apply recipes to build
# a list of series KW dicts.
# note: at entry, we only have those preprocessed args which were passed in... no default values yet
function _plot!(plt::Plot, d::KW, args...)
# d = plt.user_attr
d[:plot_object] = plt
# the grouping mechanism is a recipe on a GroupBy object
# we simply add the GroupBy object to the front of the args list to allow
# the recipe to be applied
if haskey(d, :group)
args = (extractGroupArgs(d[:group], args...), args...)
end
# for plotting recipes, swap out the args and update the parameter dictionary
args = _apply_recipe(d, args...; kw...)
# we are keeping a queue of series that still need to be processed.
# each pass through the loop, we pop one off and apply the recipe.
# the recipe will return a list a Series objects... the ones that are
# finished (no more args) get added to the kw_list, and the rest go into the queue
# for processing.
kw_list = KW[]
still_to_process = isempty(args) ? [] : [RecipeData(copy(d), args)]
while !isempty(still_to_process)
dumpdict(d, "After plot! preprocessing")
# @show groupargs map(typeof, args)
# grab the first in line to be processed and pass it through apply_recipe
# to generate a list of RecipeData objects (data + attributes)
next_series = shift!(still_to_process)
for recipedata in RecipesBase.apply_recipe(next_series.d, next_series.args...)
warnOnUnsupportedArgs(plt.backend, d)
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
if !(typeof(recipedata) <: RecipeData)
error("Inputs couldn't be processed... expected RecipeData but got: $recipedata")
end
# just in case the backend needs to set up the plot (make it current or something)
_before_add_series(plt)
if isempty(recipedata.args)
# when the arg tuple is empty, that means there's nothing left to recursively
# process... finish up and add to the kw_list
kw = recipedata.d
_add_markershape(kw)
# # grouping
# groupargs = get(d, :group, nothing) == nothing ? [nothing] : [extractGroupArgs(d[:group], args...)]
# # @show groupargs
# if there was a grouping, filter the data here
_filter_input_data!(kw)
# TODO: get the GroupBy object (or nothing), and loop through the groups, doing the following section many times
# dumpdict(d, "before", true)
groupby = if haskey(d, :group)
extractGroupArgs(d[:group], args...)
else
nothing
end
# dumpdict(d, "after", true)
# @show groupby map(typeof, args)
# map marker_z if it's a Function
if isa(get(kw, :marker_z, nothing), Function)
# TODO: should this take y and/or z as arguments?
kw[:marker_z] = map(kw[:marker_z], kw[:x])
end
_add_series(plt, d, groupby, args...)
# convert a ribbon into a fillrange
if get(kw, :ribbon, nothing) != nothing
make_fillrange_from_ribbon(kw)
end
#
# # get the list of dictionaries, one per series
# @show groupargs map(typeof, args)
# dumpdict(d, "before process_inputs")
# process_inputs(plt, d, groupargs..., args...)
# dumpdict(d, "after process_inputs", true)
# seriesArgList, xmeta, ymeta = build_series_args(plt, d)
# # 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()
# setTicksFromStringVector(d, di, :x, :xticks)
# setTicksFromStringVector(d, di, :y, :yticks)
# end
#
# # remove plot args
# for k in keys(_plotDefaults)
# delete!(di, k)
# end
#
# dumpdict(di, "Series $i")
#
# _add_series(plt.backend, plt; di...)
# end
# add the plot index
plt.n += 1
kw[:series_plotindex] = plt.n
_add_annotations(plt, d)
# check that the backend will support the command and add it to the list
warnOnUnsupported_scales(plt.backend, kw)
push!(kw_list, kw)
warnOnUnsupportedScales(plt.backend, d)
# handle error bars by creating new recipedata data... these will have
# the same recipedata index as the recipedata they are copied from
for esym in (:xerror, :yerror)
if get(d, esym, nothing) != nothing
# we make a copy of the KW and apply an errorbar recipe
errkw = copy(kw)
errkw[:seriestype] = esym
errkw[:label] = ""
errkw[:primary] = false
push!(kw_list, errkw)
end
end
# handle smoothing by adding a new series
if get(d, :smooth, false)
x, y = kw[:x], kw[:y]
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
sx = [minimum(x), maximum(x)]
sy = β * sx + α
push!(kw_list, merge(copy(kw), KW(
:seriestype => :path,
:x => sx,
:y => sy,
:fillrange => nothing,
:label => "",
:primary => false,
)))
# add title, axis labels, ticks, etc
if !haskey(d, :subplot)
merge!(plt.plotargs, d)
dumpdict(plt.plotargs, "Updating plot items")
_update_plot(plt, plt.plotargs)
# don't allow something else to handle it
d[:smooth] = false
end
else
# args are non-empty, so there's still processing to do... add it back to the queue
push!(still_to_process, recipedata)
end
end
end
_update_plot_pos_size(plt, d)
# merge in anything meant for plot/subplot/axis
for kw in kw_list
for (k,v) in kw
for defdict in (_plot_defaults,)
# _subplot_defaults,
# _axis_defaults,
# _axis_defaults_byletter)
if haskey(defdict, k)
d[k] = pop!(kw, k)
end
end
end
end
# TODO: init subplots here
_update_plot_args(plt, d)
if !plt.init
plt.o = _create_backend_figure(plt)
# DD(d)
# create the layout and subplots from the inputs
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
for (idx,sp) in enumerate(plt.subplots)
sp.plt = plt
sp.attr[:subplot_index] = idx
end
plt.init = true
end
# handle inset subplots
insets = plt[:inset_subplots]
if insets != nothing
for inset in insets
parent, bb = is_2tuple(inset) ? inset : (nothing, inset)
P = typeof(parent)
if P <: Integer
parent = plt.subplots[parent]
elseif P == Symbol
parent = plt.spmap[parent]
else
parent = plt.layout
end
sp = Subplot(backend(), parent=parent)
sp.plt = plt
sp.attr[:relative_bbox] = bb
push!(plt.subplots, sp)
sp.attr[:subplot_index] = length(plt.subplots)
push!(plt.inset_subplots, sp)
end
end
# we'll keep a map of subplot to an attribute override dict.
# any series which belong to that subplot
sp_attrs = Dict{Subplot,Any}()
for (i,kw) in enumerate(kw_list)
# get the Subplot object to which the series belongs
sp = get(kw, :subplot, :auto)
sp = if sp == :auto
mod1(i,length(plt.subplots))
else
slice_arg(sp, i)
end
sp = kw[:subplot] = get_subplot(plt, sp)
# idx = get_subplot_index(plt, sp)
attr = KW()
for (k,v) in kw
for defdict in (_subplot_defaults,
_axis_defaults,
_axis_defaults_byletter)
if haskey(defdict, k)
attr[k] = pop!(kw, k)
end
end
end
sp_attrs[sp] = attr
end
# # just in case the backend needs to set up the plot (make it current or something)
# _prepare_plot_object(plt)
# first apply any args for the subplots
for (idx,sp) in enumerate(plt.subplots)
# if we picked up any subplot-specific overrides, merge them here
attr = merge(d, get(sp_attrs, sp, KW()))
# DD(attr, "sp$idx")
_update_subplot_args(plt, sp, attr, idx, remove_pair = false)
end
# do we need to link any axes together?
link_axes!(plt.layout, plt[:link])
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!!
# !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
# this is it folks!
# TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes
for (i,kw) in enumerate(kw_list)
command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
# # get the Subplot object to which the series belongs
# sp = get(kw, :subplot, :auto)
# sp = if sp == :auto
# mod1(i,length(plt.subplots))
# else
# slice_arg(sp, i)
# end
# sp = kw[:subplot] = get_subplot(plt, sp)
sp = kw[:subplot]
idx = get_subplot_index(plt, sp)
# strip out series annotations (those which are based on series x/y coords)
# and add them to the subplot attr
sp_anns = annotations(sp[:annotations])
anns = annotations(pop!(kw, :series_annotations, []))
if length(anns) > 0
x, y = kw[:x], kw[:y]
nx, ny, na = map(length, (x,y,anns))
n = max(nx, ny, na)
anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(anns[mod1(i,na)])) for i=1:n]
end
sp.attr[:annotations] = vcat(sp_anns, anns)
# we update subplot args in case something like the color palatte is part of the recipe
_update_subplot_args(plt, sp, kw, idx)
# set default values, select from attribute cycles, and generally set the final attributes
_add_defaults!(kw, plt, sp, command_idx)
# now we have a fully specified series, with colors chosen. we must recursively handle
# series recipes, which dispatch on seriestype. If a backend does not natively support a seriestype,
# we check for a recipe that will convert that series type into one made up of lower-level components.
# For example, a histogram is just a bar plot with binned data, a bar plot is really a filled step plot,
# and a step plot is really just a path. So any backend that supports drawing a path will implicitly
# be able to support step, bar, and histogram plots (and any recipes that use those components).
_apply_series_recipe(plt, kw)
end
current(plt)
# NOTE: lets ignore the show param and effectively use the semicolon at the end of the REPL statement
# note: lets ignore the show param and effectively use the semicolon at the end of the REPL statement
# # do we want to show it?
if haskey(d, :show) && d[:show]
# if haskey(d, :show) && d[:show]
if get(d, :show, default(:show))
gui()
end
plt
end
# 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))
function _replace_linewidth(d::KW)
# get a good default linewidth... 0 for surface and heatmaps
if get(d, :linewidth, :auto) == :auto
d[:linewidth] = (get(d, :seriestype, :path) in (:surface,:heatmap,:image) ? 0 : 1)
end
end
filter_data(v::AVec, idxfilter::AVec{Int}) = v[idxfilter]
filter_data(v, idxfilter) = v
# we're getting ready to display/output. prep for layout calcs, then update
# the plot object after
function prepare_output(plt::Plot)
_before_layout_calcs(plt)
function filter_data!(d::KW, idxfilter)
for s in (:x, :y, :z)
d[s] = filter_data(get(d, s, nothing), idxfilter)
w, h = plt.attr[:size]
plt.layout.bbox = BoundingBox(0mm, 0mm, w*px, h*px)
# One pass down and back up the tree to compute the minimum padding
# of the children on the perimeter. This is an backend callback.
_update_min_padding!(plt.layout)
for sp in plt.inset_subplots
_update_min_padding!(sp)
end
# now another pass down, to update the bounding boxes
update_child_bboxes!(plt.layout)
# update those bounding boxes of inset subplots
update_inset_bboxes!(plt)
# the backend callback, to reposition subplots, etc
_update_plot_object(plt)
end
# 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()
setTicksFromStringVector(d, di, :x, :xticks)
setTicksFromStringVector(d, di, :y, :yticks)
end
# remove plot args
for k in keys(_plotDefaults)
delete!(di, k)
end
dumpdict(di, "Series $i")
_add_series(plt.backend, plt; di...)
end
function prepared_object(plt::Plot)
prepare_output(plt)
plt.o
end
# --------------------------------------------------------------------
# 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
# function get_indices(orig, labels)
# Int[findnext(labels, l, 1) for l in orig]
# end
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
# --------------------------------------------------------------------
_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
# # TODO: remove?? this is the old way of handling discrete data... should be
# # replaced by the Axis type and logic
# function setTicksFromStringVector(plt::Plot, d::KW, di::KW, letter)
# sym = Symbol(letter)
# ticksym = Symbol(letter * "ticks")
# pargs = plt.attr
# v = di[sym]
#
# # do we really want to do this?
# typeof(v) <: AbstractArray || return
# isempty(v) && return
# trueOrAllTrue(_ -> typeof(_) <: AbstractString, v) || return
#
# # compute the ticks and labels
# ticks, labels = if ticksType(pargs[ticksym]) == :ticks_and_labels
# # extend the existing ticks and labels. only add to labels if they're new!
# ticks, labels = pargs[ticksym]
# newlabels = filter(_ -> !(_ in labels), unique(v))
# newticks = if isempty(ticks)
# collect(1:length(newlabels))
# else
# maximum(ticks) + collect(1:length(newlabels))
# end
# ticks = vcat(ticks, newticks)
# labels = vcat(labels, newlabels)
# ticks, labels
# else
# # create new ticks and labels
# newlabels = unique(v)
# collect(1:length(newlabels)), newlabels
# end
#
# d[ticksym] = ticks, labels
# plt.attr[ticksym] = ticks, labels
#
# # add an origsym field so that later on we can re-compute the x vector if ticks change
# origsym = Symbol(letter * "orig")
# di[origsym] = v
# di[sym] = get_indices(v, labels)
#
# # loop through existing plt.seriesargs and adjust indices if there is an origsym key
# for sargs in plt.seriesargs
# if haskey(sargs, origsym)
# # TODO: might need to call the setindex function instead to trigger a plot update for some backends??
# sargs[sym] = get_indices(sargs[origsym], labels)
# end
# end
# end
# --------------------------------------------------------------------
function Base.copy(plt::Plot)
backend(plt.backend)
plt2 = plot(; plt.plotargs...)
for sargs in plt.seriesargs
sargs = filter((k,v) -> haskey(_seriesDefaults,k), sargs)
plot!(plt2; sargs...)
end
plt2
end
# --------------------------------------------------------------------
# function Base.copy(plt::Plot)
# backend(plt.backend)
# plt2 = plot(; plt.attr...)
# for sargs in plt.seriesargs
# sargs = filter((k,v) -> haskey(_series_defaults,k), sargs)
# plot!(plt2; sargs...)
# end
# plt2
# end
# --------------------------------------------------------------------
+951 -53
View File
File diff suppressed because it is too large Load Diff
+456 -343
View File
@@ -7,7 +7,7 @@
typealias FuncOrFuncs @compat(Union{Function, AVec{Function}})
all3D(d::KW) = trueOrAllTrue(lt -> lt in (:contour, :heatmap, :surface, :wireframe), get(d, :linetype, :none))
all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :heatmap, :surface, :wireframe, :contour3d), get(d, :seriestype, :none))
# missing
convertToAnyVector(v::@compat(Void), d::KW) = Any[nothing], nothing
@@ -30,14 +30,22 @@ function convertToAnyVector{T<:Number}(v::AMat{T}, d::KW)
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
# # 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
@@ -54,6 +62,12 @@ function convertToAnyVector(v::AVec, d::KW)
end
end
convertToAnyVector(t::Tuple, d::KW) = Any[t], nothing
function convertToAnyVector(args...)
error("In convertToAnyVector, could not handle the argument types: $(map(typeof, args[1:end-1]))")
end
# --------------------------------------------------------------------
@@ -75,11 +89,15 @@ compute_z(x, y, z::AbstractMatrix) = Surface(z)
compute_z(x, y, z::Void) = nothing
compute_z(x, y, z) = copy(z)
nobigs(v::AVec{BigFloat}) = map(Float64, v)
nobigs(v::AVec{BigInt}) = map(Int64, v)
nobigs(v) = v
@noinline function compute_xyz(x, y, z)
x = compute_x(x,y,z)
y = compute_y(x,y,z)
z = compute_z(x,y,z)
x, y, z
nobigs(x), nobigs(y), nobigs(z)
end
# not allowed
@@ -89,349 +107,444 @@ 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(a -> pop!(kw, a, 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)
# if idxfilter != nothing
# xs = filter_data(xs, idxfilter)
# ys = filter_data(ys, idxfilter)
# zs = filter_data(zs, idxfilter)
# # # filter the data
# # for sym in (:x, :y, :z)
# # @show "before" sym, d[sym], idxfilter
# # d[sym] = filter_data(get(d, sym, nothing), idxfilter)
# # @show "after" sym, d[sym], idxfilter
# # end
# 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")
# @show numUncounted i n commandIndex convertSeriesIndex(plt, n)
d = getSeriesArgs(plt.backend, getplotargs(plt, n), d, commandIndex, convertSeriesIndex(plt, n), n)
dumpdict(d, "after getSeriesArgs")
# @show map(typeof, (xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]))
d[:x], d[:y], d[:z] = compute_xyz(xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)])
# @show map(typeof, (d[:x], d[:y], d[:z]))
# # NOTE: this should be handled by the time it gets here
lt = d[:linetype]
# if isa(d[:y], Surface)
# if lt in (:contour, :heatmap, :surface, :wireframe)
# z = d[:y]
# d[:y] = 1:size(z,2)
# d[:z] = z
# end
# end
# if haskey(d, :idxfilter)
# idxfilter = pop!(d, :idxfilter)
# d[:x] = d[:x][idxfilter]
# d[:y] = d[:y][idxfilter]
# end
# 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
# map functions to vectors
if isa(d[:zcolor], Function)
d[:zcolor] = map(d[:zcolor], d[:x])
end
if isa(d[:fillrange], Function)
d[:fillrange] = map(d[:fillrange], d[:x])
end
# cleanup those fields that were used only for generating kw args
# delete!(d, :dataframe)
# for k in (:idxfilter, :numUncounted, :dataframe)
# delete!(d, k)
# end
# 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
# --------------------------------------------------------------------
# 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
if !(get(d, :linetype, :none) in _3dTypes)
d[:linetype] = :path3d
end
d[:x], d[:y], d[:z] = x, y, zvec
end
# surface-like... function
function process_inputs(plt::AbstractPlot, d::KW, x::AVec, y::AVec, zf::Function)
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{T<:Number}(plt::AbstractPlot, d::KW, x::AVec, y::AVec, zmat::AMat{T})
@assert size(zmat) == (length(x), length(y))
if !issorted(x) || !issorted(y)
x_idx = sortperm(x)
y_idx = sortperm(y)
x, y = x[x_idx], y[y_idx]
zmat = zmat[x_idx, y_idx]
end
d[:x], d[:y], d[:z] = x, y, Surface{Matrix{Float64}}(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)
# # 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
# ret, nothing, nothing # TODO: handle passing meta through
#
# xs, xmeta = convertToAnyVector(x, kw)
# ys, ymeta = convertToAnyVector(y, kw)
# zs, zmeta = convertToAnyVector(z, kw)
#
# fr = pop!(kw, :fillrange, nothing)
# fillranges, _ = if typeof(fr) <: Number
# ([fr],nothing)
# else
# convertToAnyVector(fr, kw)
# end
#
# mx = length(xs)
# my = length(ys)
# mz = length(zs)
# ret = Any[]
# for i in 1:max(mx, my, mz)
#
# # try to set labels using ymeta
# d = copy(kw)
# if !haskey(d, :label) && ymeta != nothing
# if isa(ymeta, Symbol)
# d[:label] = string(ymeta)
# elseif isa(ymeta, AVec{Symbol})
# d[:label] = string(ymeta[mod1(i,length(ymeta))])
# end
# end
#
# # build the series arg dict
# numUncounted = pop!(d, :numUncounted, 0)
# commandIndex = i + numUncounted
# n = plt.n + i
#
# dumpdict(d, "before getSeriesArgs")
# d = getSeriesArgs(plt.backend, getattr(plt, n), d, commandIndex, convertSeriesIndex(plt, n), n)
# dumpdict(d, "after getSeriesArgs")
#
# d[:x], d[:y], d[:z] = compute_xyz(xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)])
# st = d[:seriestype]
#
# # for seriestype `line`, need to sort by x values
# if st == :line
# # order by x
# indices = sortperm(d[:x])
# d[:x] = d[:x][indices]
# d[:y] = d[:y][indices]
# d[:seriestype] = :path
# end
#
# # special handling for missing x in box plot... all the same category
# if st == :box && xs[mod1(i,mx)] == nothing
# d[:x] = ones(Int, length(d[:y]))
# end
#
# # map functions to vectors
# if isa(d[:marker_z], Function)
# d[:marker_z] = map(d[:marker_z], d[:x])
# end
#
# # @show fillranges
# d[:fillrange] = fillranges[mod1(i,length(fillranges))]
# if isa(d[:fillrange], Function)
# d[:fillrange] = map(d[:fillrange], d[:x])
# end
#
# # handle error bars
# for esym in (:xerror, :yerror)
# if get(d, esym, nothing) != nothing
# # we make a copy of the KW and apply an errorbar recipe
# append!(ret, apply_series_recipe(copy(d), Val{esym}))
# end
# end
#
# # handle ribbons
# if get(d, :ribbon, nothing) != nothing
# rib = d[:ribbon]
# d[:fillrange] = (d[:y] - rib, d[:y] + rib)
# end
#
# # handle quiver plots
# # either a series of velocity vectors are passed in (`:quiver` keyword),
# # or we just add arrows to the path
#
# # if st == :quiver
# # d[:seriestype] = st = :path
# # d[:linewidth] = 0
# # end
# if get(d, :quiver, nothing) != nothing
# append!(ret, apply_series_recipe(copy(d), Val{:quiver}))
# elseif st == :quiver
# d[:seriestype] = st = :path
# d[:arrow] = arrow()
# end
#
# # now that we've processed a given series... optionally split into
# # multiple dicts through a recipe (for example, a box plot is split into component
# # parts... polygons, lines, and scatters)
# # note: we pass in a Val type (i.e. Val{:box}) so that we can dispatch on the seriestype
# kwlist = apply_series_recipe(d, Val{st})
# append!(ret, kwlist)
#
# # # add it to our series list
# # push!(ret, d)
# end
#
# ret, xmeta, ymeta
# end
#
#
# # --------------------------------------------------------------------
# # process_inputs
# # --------------------------------------------------------------------
#
# # These methods take a plot and the keyword arguments, and processes the input
# # arguments (x/y/z, group, etc), populating the KW dict with appropriate values.
#
# # --------------------------------------------------------------------
# # 0 arguments
# # --------------------------------------------------------------------
#
# # don't do anything
# function process_inputs(plt::AbstractPlot, d::KW)
# end
#
# # --------------------------------------------------------------------
# # 1 argument
# # --------------------------------------------------------------------
#
# function process_inputs(plt::AbstractPlot, d::KW, n::Integer)
# # d[:x], d[:y], d[:z] = zeros(0), zeros(0), zeros(0)
# d[:x] = d[:y] = d[:z] = n
# end
#
# # no special handling... assume x and z are nothing
# function process_inputs(plt::AbstractPlot, d::KW, y)
# d[:y] = y
# end
#
# # matrix... is it z or y?
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, mat::AMat{T})
# if all3D(d)
# n,m = size(mat)
# d[:x], d[:y], d[:z] = 1:n, 1:m, mat
# else
# d[:y] = mat
# end
# end
#
# # images - grays
# function process_inputs{T<:Gray}(plt::AbstractPlot, d::KW, mat::AMat{T})
# d[:seriestype] = :image
# n,m = size(mat)
# d[:x], d[:y], d[:z] = 1:n, 1:m, Surface(mat)
# # handle images... when not supported natively, do a hack to use heatmap machinery
# if !nativeImagesSupported()
# d[:seriestype] = :heatmap
# d[:yflip] = true
# d[:z] = Surface(convert(Matrix{Float64}, mat.surf))
# d[:fillcolor] = ColorGradient([:black, :white])
# end
# end
#
# # images - colors
# function process_inputs{T<:Colorant}(plt::AbstractPlot, d::KW, mat::AMat{T})
# d[:seriestype] = :image
# n,m = size(mat)
# d[:x], d[:y], d[:z] = 1:n, 1:m, Surface(mat)
# # handle images... when not supported natively, do a hack to use heatmap machinery
# if !nativeImagesSupported()
# d[:yflip] = true
# imageHack(d)
# end
# end
#
#
# # plotting arbitrary shapes/polygons
# function process_inputs(plt::AbstractPlot, d::KW, shape::Shape)
# d[:x], d[:y] = shape_coords(shape)
# d[:seriestype] = :shape
# end
# function process_inputs(plt::AbstractPlot, d::KW, shapes::AVec{Shape})
# d[:x], d[:y] = shape_coords(shapes)
# d[:seriestype] = :shape
# end
# function process_inputs(plt::AbstractPlot, d::KW, shapes::AMat{Shape})
# x, y = [], []
# for j in 1:size(shapes, 2)
# tmpx, tmpy = shape_coords(vec(shapes[:,j]))
# push!(x, tmpx)
# push!(y, tmpy)
# end
# d[:x], d[:y] = x, y
# d[:seriestype] = :shape
# end
#
#
# # function without range... use the current range of the x-axis
# function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs)
# process_inputs(plt, d, f, xmin(plt), xmax(plt))
# end
#
# # --------------------------------------------------------------------
# # 2 arguments
# # --------------------------------------------------------------------
#
# function process_inputs(plt::AbstractPlot, d::KW, x, y)
# d[:x], d[:y] = x, y
# end
#
# # if functions come first, just swap the order (not to be confused with parametric functions...
# # as there would be more than one function passed in)
# function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs, x)
# @assert !(typeof(x) <: FuncOrFuncs) # otherwise we'd hit infinite recursion here
# process_inputs(plt, d, x, f)
# end
#
# # --------------------------------------------------------------------
# # 3 arguments
# # --------------------------------------------------------------------
#
# # no special handling... just pass them through
# function process_inputs(plt::AbstractPlot, d::KW, x, y, z)
# d[:x], d[:y], d[:z] = x, y, z
# end
#
# # 3d line or scatter
# function process_inputs(plt::AbstractPlot, d::KW, x::AVec, y::AVec, zvec::AVec)
# # default to path3d if we haven't set a 3d seriestype
# st = get(d, :seriestype, :none)
# if st == :scatter
# d[:seriestype] = :scatter3d
# elseif !(st in _3dTypes)
# d[:seriestype] = :path3d
# end
# d[:x], d[:y], d[:z] = x, y, zvec
# end
#
# # surface-like... function
# function process_inputs{TX,TY}(plt::AbstractPlot, d::KW, x::AVec{TX}, y::AVec{TY}, zf::Function)
# x = TX <: Number ? sort(x) : x
# y = TY <: Number ? sort(y) : y
# # x, y = sort(x), sort(y)
# d[:z] = Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
# d[:x], d[:y] = x, y
# end
#
# # surface-like... matrix grid
# function process_inputs{TX,TY,TZ}(plt::AbstractPlot, d::KW, x::AVec{TX}, y::AVec{TY}, zmat::AMat{TZ})
# # @assert size(zmat) == (length(x), length(y))
# # if TX <: Number && !issorted(x)
# # idx = sortperm(x)
# # x, zmat = x[idx], zmat[idx, :]
# # end
# # if TY <: Number && !issorted(y)
# # idx = sortperm(y)
# # y, zmat = y[idx], zmat[:, idx]
# # end
# d[:x], d[:y], d[:z] = x, y, Surface{Matrix{TZ}}(zmat)
# if !like_surface(get(d, :seriestype, :none))
# d[:seriestype] = :contour
# end
# end
#
# # surfaces-like... general x, y grid
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, x::AMat{T}, y::AMat{T}, zmat::AMat{T})
# @assert size(zmat) == size(x) == size(y)
# # d[:x], d[:y], d[:z] = Any[x], Any[y], Surface{Matrix{Float64}}(zmat)
# d[:x], d[:y], d[:z] = map(Surface{Matrix{Float64}}, (x, y, zmat))
# if !like_surface(get(d, :seriestype, :none))
# d[:seriestype] = :contour
# end
# end
#
#
# # --------------------------------------------------------------------
# # Parametric functions
# # --------------------------------------------------------------------
#
# # special handling... xmin/xmax with function(s)
# function process_inputs(plt::AbstractPlot, d::KW, f::FuncOrFuncs, xmin::Number, xmax::Number)
# width = get(plt.attr, :size, (100,))[1]
# x = linspace(xmin, xmax, width)
# process_inputs(plt, d, x, f)
# end
#
# # special handling... xmin/xmax with parametric function(s)
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u))
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u))
# process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, linspace(umin, umax, numPoints))
#
# # special handling... 3D parametric function(s)
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, u::AVec{T}) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u))
# process_inputs{T<:Number}(plt::AbstractPlot, d::KW, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs) = process_inputs(plt, d, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u))
# process_inputs(plt::AbstractPlot, d::KW, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, umin::Number, umax::Number, numPoints::Int = 1000) = process_inputs(plt, d, fx, fy, fz, linspace(umin, umax, numPoints))
#
#
# # --------------------------------------------------------------------
# # Lists of tuples and FixedSizeArrays
# # --------------------------------------------------------------------
#
# # if we get an unhandled tuple, just splat it in
# function process_inputs(plt::AbstractPlot, d::KW, tup::Tuple)
# process_inputs(plt, d, tup...)
# end
#
# # (x,y) tuples
# function process_inputs{R1<:Number,R2<:Number}(plt::AbstractPlot, d::KW, xy::AVec{Tuple{R1,R2}})
# process_inputs(plt, d, unzip(xy)...)
# end
# function process_inputs{R1<:Number,R2<:Number}(plt::AbstractPlot, d::KW, xy::Tuple{R1,R2})
# process_inputs(plt, d, [xy[1]], [xy[2]])
# end
#
# # (x,y,z) tuples
# function process_inputs{R1<:Number,R2<:Number,R3<:Number}(plt::AbstractPlot, d::KW, xyz::AVec{Tuple{R1,R2,R3}})
# process_inputs(plt, d, unzip(xyz)...)
# end
# function process_inputs{R1<:Number,R2<:Number,R3<:Number}(plt::AbstractPlot, d::KW, xyz::Tuple{R1,R2,R3})
# process_inputs(plt, d, [xyz[1]], [xyz[2]], [xyz[3]])
# end
#
# # 2D FixedSizeArrays
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xy::AVec{FixedSizeArrays.Vec{2,T}})
# process_inputs(plt, d, unzip(xy)...)
# end
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xy::FixedSizeArrays.Vec{2,T})
# process_inputs(plt, d, [xy[1]], [xy[2]])
# end
#
# # 3D FixedSizeArrays
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xyz::AVec{FixedSizeArrays.Vec{3,T}})
# process_inputs(plt, d, unzip(xyz)...)
# end
# function process_inputs{T<:Number}(plt::AbstractPlot, d::KW, xyz::FixedSizeArrays.Vec{3,T})
# process_inputs(plt, d, [xyz[1]], [xyz[2]], [xyz[3]])
# end
#
# # --------------------------------------------------------------------
# # handle grouping
# # --------------------------------------------------------------------
#
# # function process_inputs(plt::AbstractPlot, d::KW, groupby::GroupBy, args...)
# # ret = Any[]
# # error("unfinished after series reorg")
# # for (i,glab) in enumerate(groupby.groupLabels)
# # # TODO: don't automatically overwrite labels
# # kwlist, xmeta, ymeta = process_inputs(plt, d, args...,
# # idxfilter = groupby.groupIds[i],
# # label = string(glab),
# # numUncounted = length(ret)) # we count the idx from plt.n + numUncounted + i
# # append!(ret, kwlist)
# # end
# # ret, nothing, nothing # TODO: handle passing meta through
# # end
# --------------------------------------------------------------------
# For DataFrame support. Imports DataFrames and defines the necessary methods which support them.
# --------------------------------------------------------------------
function setup_dataframes()
@require DataFrames begin
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
# function setup_dataframes()
# @require DataFrames begin
# # @eval begin
# # import DataFrames
#
# DFS = Union{Symbol, AbstractArray{Symbol}}
#
# function handle_dfs(df::DataFrames.AbstractDataFrame, d::KW, letter, dfs::DFS)
# if isa(dfs, Symbol)
# get!(d, Symbol(letter * "label"), string(dfs))
# collect(df[dfs])
# else
# get!(d, :label, reshape(dfs, 1, length(dfs)))
# Any[collect(df[s]) for s in dfs]
# end
# end
#
# function handle_group(df::DataFrames.AbstractDataFrame, d::KW)
# if haskey(d, :group)
# g = d[:group]
# if isa(g, Symbol)
# d[:group] = collect(df[g])
# end
# end
# end
#
# @recipe function plot(df::DataFrames.AbstractDataFrame, sy::DFS)
# handle_group(df, d)
# handle_dfs(df, d, "y", sy)
# end
#
# @recipe function plot(df::DataFrames.AbstractDataFrame, sx::DFS, sy::DFS)
# handle_group(df, d)
# x = handle_dfs(df, d, "x", sx)
# y = handle_dfs(df, d, "y", sy)
# x, y
# end
#
# @recipe function plot(df::DataFrames.AbstractDataFrame, sx::DFS, sy::DFS, sz::DFS)
# handle_group(df, d)
# x = handle_dfs(df, d, "x", sx)
# y = handle_dfs(df, d, "y", sy)
# z = handle_dfs(df, d, "z", sz)
# x, y, z
# end
#
# # get_data(df::DataFrames.AbstractDataFrame, arg::Symbol) = df[arg]
# # get_data(df::DataFrames.AbstractDataFrame, arg) = arg
# #
# # function process_inputs(plt::AbstractPlot, d::KW, df::DataFrames.AbstractDataFrame, args...)
# # # d[:dataframe] = df
# # process_inputs(plt, d, map(arg -> get_data(df, arg), args)...)
# # end
# #
# # # expecting the column name of a dataframe that was passed in... anything else should error
# # function extractGroupArgs(s::Symbol, df::DataFrames.AbstractDataFrame, args...)
# # if haskey(df, s)
# # return extractGroupArgs(df[s])
# # else
# # error("Got a symbol, and expected that to be a key in d[:dataframe]. s=$s d=$d")
# # end
# # end
#
# # function getDataFrameFromKW(d::KW)
# # get(d, :dataframe) do
# # error("Missing dataframe argument!")
# # end
# # end
#
# # # the conversion functions for when we pass symbols or vectors of symbols to reference dataframes
# # convertToAnyVector(s::Symbol, d::KW) = Any[getDataFrameFromKW(d)[s]], s
# # convertToAnyVector(v::AVec{Symbol}, d::KW) = (df = getDataFrameFromKW(d); Any[df[s] for s in v]), v
#
# end
# end
+416
View File
@@ -0,0 +1,416 @@
# we are going to build recipes to do the processing and splitting of the args
function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int)
pkg = plt.backend
globalIndex = d[:series_plotindex]
# add default values to our dictionary, being careful not to delete what we just added!
for (k,v) in _series_defaults
slice_arg!(d, d, k, v, commandIndex, remove_pair = false)
end
# this is how many series belong to this subplot
plotIndex = count(series -> series.d[:subplot] === sp && series.d[:primary], plt.series_list)
if get(d, :primary, true)
plotIndex += 1
end
aliasesAndAutopick(d, :linestyle, _styleAliases, supported_styles(pkg), plotIndex)
aliasesAndAutopick(d, :markershape, _markerAliases, supported_markers(pkg), plotIndex)
# update color
d[:seriescolor] = getSeriesRGBColor(d[:seriescolor], sp, plotIndex)
# update colors
for csym in (:linecolor, :markercolor, :fillcolor)
d[csym] = if d[csym] == :match
if has_black_border_for_default(d[:seriestype]) && csym == :linecolor
:black
else
d[:seriescolor]
end
else
getSeriesRGBColor(d[csym], sp, plotIndex)
end
end
# update markerstrokecolor
c = d[:markerstrokecolor]
c = if c == :match
sp[:foreground_color_subplot]
else
getSeriesRGBColor(c, sp, plotIndex)
end
d[:markerstrokecolor] = c
# update alphas
for asym in (:linealpha, :markeralpha, :fillalpha)
if d[asym] == nothing
d[asym] = d[:seriesalpha]
end
end
if d[:markerstrokealpha] == nothing
d[:markerstrokealpha] = d[:markeralpha]
end
# scatter plots don't have a line, but must have a shape
if d[:seriestype] in (:scatter, :scatter3d)
d[:linewidth] = 0
if d[:markershape] == :none
d[:markershape] = :circle
end
end
# set label
label = d[:label]
label = (label == "AUTO" ? "y$globalIndex" : label)
d[:label] = label
_replace_linewidth(d)
d
end
# -------------------------------------------------------------------
# -------------------------------------------------------------------
# instead of process_inputs:
# ensure we dispatch to the slicer
immutable SliceIt end
# the catch-all recipes
@recipe function f(::Type{SliceIt}, x, y, z)
# @show "HERE", typeof((x,y,z))
xs, _ = convertToAnyVector(x, d)
ys, _ = convertToAnyVector(y, d)
zs, _ = convertToAnyVector(z, d)
fr = pop!(d, :fillrange, nothing)
fillranges, _ = if typeof(fr) <: Number
([fr],nothing)
else
convertToAnyVector(fr, d)
end
mf = length(fillranges)
# @show zs
mx = length(xs)
my = length(ys)
mz = length(zs)
# ret = Any[]
for i in 1:max(mx, my, mz)
# add a new series
di = copy(d)
xi, yi, zi = xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]
# @show i, typeof((xi, yi, zi))
di[:x], di[:y], di[:z] = compute_xyz(xi, yi, zi)
# @show i, typeof((di[:x], di[:y], di[:z]))
# handle fillrange
fr = fillranges[mod1(i,mf)]
di[:fillrange] = isa(fr, Function) ? map(fr, di[:x]) : fr
# @show i, di[:x], di[:y], di[:z]
push!(series_list, RecipeData(di, ()))
end
nothing # don't add a series for the main block
end
# this is the default "type recipe"... just pass the object through
@recipe f{T<:Any}(::Type{T}, v::T) = v
# this should catch unhandled "series recipes" and error with a nice message
@recipe f{V<:Val}(::Type{V}, x, y, z) = error("The backend must not support the series type $V, and there isn't a series recipe defined.")
_apply_type_recipe(d, v) = RecipesBase.apply_recipe(d, typeof(v), v)[1].args[1]
# handle "type recipes" by converting inputs, and then either re-calling or slicing
@recipe function f(x, y, z)
did_replace = false
newx = _apply_type_recipe(d, x)
x === newx || (did_replace = true)
newy = _apply_type_recipe(d, y)
y === newy || (did_replace = true)
newz = _apply_type_recipe(d, z)
z === newz || (did_replace = true)
if did_replace
newx, newy, newz
else
SliceIt, x, y, z
end
end
@recipe function f(x, y)
did_replace = false
newx = _apply_type_recipe(d, x)
x === newx || (did_replace = true)
newy = _apply_type_recipe(d, y)
y === newy || (did_replace = true)
if did_replace
newx, newy
else
SliceIt, x, y, nothing
end
end
@recipe function f(y)
newy = _apply_type_recipe(d, y)
if y !== newy
newy
else
SliceIt, nothing, y, nothing
end
end
# if there's more than 3 inputs, it can't be passed directly to SliceIt
# so we'll apply_type_recipe to all of them
@recipe function f(v1, v2, v3, v4, vrest...)
did_replace = false
newargs = map(v -> begin
newv = _apply_type_recipe(d, v)
if newv !== v
did_replace = true
end
newv
end, (v1, v2, v3, v4, vrest...))
if !did_replace
error("Couldn't process recipe args: $(map(typeof, (v1, v2, v3, v4, vrest...)))")
end
newargs
end
# @recipe f(x, y, z) = SliceIt, apply_recipe(typeof(x), x), apply_recipe(typeof(y), y), apply_recipe(typeof(z), z)
# @recipe f(x, y) = SliceIt, apply_recipe(typeof(x), x), apply_recipe(typeof(y), y), nothing
# @recipe f(y) = SliceIt, nothing, apply_recipe(typeof(y), y), nothing
# # pass these through to the slicer
# @recipe f(x, y, z) = SliceIt, x, y, z
# @recipe f(x, y) = SliceIt, x, y, nothing
# @recipe f(y) = SliceIt, nothing, y, nothing
# # --------------------------------------------------------------------
# # 1 argument
# # --------------------------------------------------------------------
@recipe f(n::Integer) = n, n, n
# return a surface if this is a 3d plot, otherwise let it be sliced up
@recipe function f{T<:Number}(mat::AMat{T})
if all3D(d)
n,m = size(mat)
SliceIt, 1:m, 1:n, Surface(mat)
else
SliceIt, nothing, mat, nothing
end
end
# # images - grays
@recipe function f{T<:Gray}(mat::AMat{T})
if nativeImagesSupported()
seriestype := :image
n, m = size(mat)
SliceIt, 1:m, 1:n, Surface(mat)
else
seriestype := :heatmap
yflip --> true
fillcolor --> ColorGradient([:black, :white])
SliceIt, 1:m, 1:n, Surface(convert(Matrix{Float64}, mat))
end
end
# # images - colors
@recipe function f{T<:Colorant}(mat::AMat{T})
if nativeImagesSupported()
seriestype := :image
n, m = size(mat)
SliceIt, 1:m, 1:n, Surface(mat)
else
seriestype := :heatmap
yflip --> true
z, d[:fillcolor] = replace_image_with_heatmap(mat)
SliceIt, 1:m, 1:n, Surface(z)
end
end
#
# # plotting arbitrary shapes/polygons
@recipe function f(shape::Shape)
seriestype := :shape
shape_coords(shape)
end
@recipe function f(shapes::AVec{Shape})
seriestype := :shape
shape_coords(shapes)
end
@recipe function f(shapes::AMat{Shape})
for j in 1:size(shapes,2)
# create one series for each column
# @series shape_coords(vec(shapes[:,j]))
di = copy(d)
push!(series_list, RecipeData(di, shape_coords(vec(shapes[:,j]))))
end
nothing # don't create a series for the main block
end
#
#
# # function without range... use the current range of the x-axis
@recipe function f(f::FuncOrFuncs)
plt = d[:plot_object]
f, xmin(plt), xmax(plt)
end
#
# # --------------------------------------------------------------------
# # 2 arguments
# # --------------------------------------------------------------------
#
#
# # if functions come first, just swap the order (not to be confused with parametric functions...
# # as there would be more than one function passed in)
@recipe function f(f::FuncOrFuncs, x)
@assert !(typeof(x) <: FuncOrFuncs) # otherwise we'd hit infinite recursion here
x, f
end
#
# # --------------------------------------------------------------------
# # 3 arguments
# # --------------------------------------------------------------------
#
#
# # 3d line or scatter
@recipe function f(x::AVec, y::AVec, z::AVec)
st = get(d, :seriestype, :none)
if st == :scatter
d[:seriestype] = :scatter3d
elseif !is3d(st)
d[:seriestype] = :path3d
end
SliceIt, x, y, z
end
@recipe function f(x::AMat, y::AMat, z::AMat)
st = get(d, :seriestype, :none)
if size(x) == size(y) == size(z)
if !is3d(st)
seriestype := :path3d
end
end
SliceIt, x, y, z
end
#
# # surface-like... function
@recipe function f(x::AVec, y::AVec, zf::Function)
# x = X <: Number ? sort(x) : x
# y = Y <: Number ? sort(y) : y
SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
end
#
# # surface-like... matrix grid
@recipe function f(x::AVec, y::AVec, z::AMat)
if !like_surface(get(d, :seriestype, :none))
d[:seriestype] = :contour
end
SliceIt, x, y, Surface(z)
end
#
#
# # --------------------------------------------------------------------
# # Parametric functions
# # --------------------------------------------------------------------
#
# # special handling... xmin/xmax with parametric function(s)
@recipe f(f::FuncOrFuncs, xmin::Number, xmax::Number) = linspace(xmin, xmax, 100), f
@recipe f(fx::FuncOrFuncs, fy::FuncOrFuncs, u::AVec) = mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u)
@recipe f(fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Number, umax::Number, n = 200) = fx, fy, linspace(umin, umax, n)
#
# # special handling... 3D parametric function(s)
@recipe function f(fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, u::AVec)
mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u)
end
@recipe function f(fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, umin::Number, umax::Number, numPoints = 200)
fx, fy, fz, linspace(umin, umax, numPoints)
end
#
#
# # --------------------------------------------------------------------
# # Lists of tuples and FixedSizeArrays
# # --------------------------------------------------------------------
#
# # if we get an unhandled tuple, just splat it in
@recipe f(tup::Tuple) = tup
#
# # (x,y) tuples
@recipe f{R1<:Number,R2<:Number}(xy::AVec{Tuple{R1,R2}}) = unzip(xy)
@recipe f{R1<:Number,R2<:Number}(xy::Tuple{R1,R2}) = [xy[1]], [xy[2]]
#
# # (x,y,z) tuples
@recipe f{R1<:Number,R2<:Number,R3<:Number}(xyz::AVec{Tuple{R1,R2,R3}}) = unzip(xyz)
@recipe f{R1<:Number,R2<:Number,R3<:Number}(xyz::Tuple{R1,R2,R3}) = [xyz[1]], [xyz[2]], [xyz[3]]
# these might be points+velocity, or OHLC or something else
@recipe f{R1<:Number,R2<:Number,R3<:Number,R4<:Number}(xyuv::AVec{Tuple{R1,R2,R3,R4}}) = get(d,:seriestype,:path)==:ohlc ? OHLC[OHLC(t...) for t in xyuv] : unzip(xyuv)
@recipe f{R1<:Number,R2<:Number,R3<:Number,R4<:Number}(xyuv::Tuple{R1,R2,R3,R4}) = [xyuv[1]], [xyuv[2]], [xyuv[3]], [xyuv[4]]
#
# # 2D FixedSizeArrays
@recipe f{T<:Number}(xy::AVec{FixedSizeArrays.Vec{2,T}}) = unzip(xy)
@recipe f{T<:Number}(xy::FixedSizeArrays.Vec{2,T}) = [xy[1]], [xy[2]]
#
# # 3D FixedSizeArrays
@recipe f{T<:Number}(xyz::AVec{FixedSizeArrays.Vec{3,T}}) = unzip(xyz)
@recipe f{T<:Number}(xyz::FixedSizeArrays.Vec{3,T}) = [xyz[1]], [xyz[2]], [xyz[3]]
#
# # --------------------------------------------------------------------
# # handle grouping
# # --------------------------------------------------------------------
# @recipe function f(groupby::GroupBy, args...)
# for (i,glab) in enumerate(groupby.groupLabels)
# # create a new series, with the label of the group, and an idxfilter (to be applied in slice_and_dice)
# # TODO: use @series instead
# @show i, glab, groupby.groupIds[i]
# di = copy(d)
# get!(di, :label, string(glab))
# get!(di, :idxfilter, groupby.groupIds[i])
# push!(series_list, RecipeData(di, args))
# end
# nothing
# end
# split the group into 1 series per group, and set the label and idxfilter for each
@recipe function f(groupby::GroupBy, args...)
for (i,glab) in enumerate(groupby.groupLabels)
@series begin
label --> string(glab)
idxfilter --> groupby.groupIds[i]
args
end
end
end
-366
View File
@@ -1,366 +0,0 @@
# ------------------------------------------------------------
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))
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
"""
Create a series of plots:
```
y = rand(100,3)
subplot(y; n = 3) # create an automatic grid, and let it figure out the nr/nc... will put plots 1 and 2 on the first row, and plot 3 by itself on the 2nd row
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...)
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
layoutarg = get(d, :layout, nothing)
if layoutarg != nothing
layout = subplotlayout(layoutarg)
else
n = get(d, :n, -1)
if n < 0
error("You must specify either layout or n when creating a subplot: ", d)
end
layout = subplotlayout(n, get(d, :nr, -1), get(d, :nc, -1))
end
# initialize the individual plots
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, d, false, false, false, (r,c) -> (nothing,nothing))
subplot!(subplt, args...; d...)
subplt
end
# ------------------------------------------------------------------------------------------------
# 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 = 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...)
validateSubplotSupported()
subplot!(current(), args...; kw...)
end
# not allowed:
function subplot!(plt::Plot, args...; kw...)
error("Can't call subplot! on a Plot!")
end
# # this adds to a specific subplot... most plot commands will flow through here
function subplot!(subplt::Subplot, args...; kw...)
# validateSubplotSupported()
d = KW(kw)
args = _preprocess_subplot(subplt, d, args)
# create the underlying object (each backend will do this differently)
# 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
# # handle grouping
# group = get(d, :group, nothing)
# if group == nothing
# groupargs = []
# else
# groupargs = [extractGroupArgs(d[:group], args...)]
# delete!(d, :group)
# end
groupby = if haskey(d, :group)
extractGroupArgs(d[:group], args...)
else
nothing
end
# dumpdict(d, "after", true)
# @show groupby map(typeof, args)
_add_series_subplot(subplt, d, groupby, args...)
# process_inputs(subplt, d, groupargs..., args...)
#
# # TODO: filter the data
#
# kwList, xmeta, ymeta = build_series_args(subplt, d)
# # kwList, xmeta, ymeta = build_series_args(subplt, groupargs..., args...; 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
_postprocess_subplot(subplt, d)
# show it automatically?
if haskey(d, :show) && d[:show]
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
+47
View File
@@ -0,0 +1,47 @@
function Subplot{T<:AbstractBackend}(::T; parent = RootLayout())
Subplot{T}(
parent,
(20mm, 5mm, 2mm, 10mm),
defaultbox,
defaultbox,
KW(),
nothing,
nothing
)
end
plotarea(sp::Subplot) = sp.plotarea
plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox)
Base.size(sp::Subplot) = (1,1)
Base.length(sp::Subplot) = 1
Base.getindex(sp::Subplot, r::Int, c::Int) = sp
leftpad(sp::Subplot) = sp.minpad[1]
toppad(sp::Subplot) = sp.minpad[2]
rightpad(sp::Subplot) = sp.minpad[3]
bottompad(sp::Subplot) = sp.minpad[4]
get_subplot(plt::Plot, sp::Subplot) = sp
get_subplot(plt::Plot, i::Integer) = plt.subplots[i]
get_subplot(plt::Plot, k) = plt.spmap[k]
get_subplot(series::Series) = series.d[:subplot]
get_subplot_index(plt::Plot, idx::Integer) = idx
get_subplot_index(plt::Plot, sp::Subplot) = findfirst(_ -> _ === sp, plt.subplots)
series_list(sp::Subplot) = filter(series -> series.d[:subplot] === sp, sp.plt.series_list)
function should_add_to_legend(series::Series)
series.d[:primary] && series.d[:label] != "" &&
!(series.d[:seriestype] in (
:hexbin,:histogram2d,:hline,:vline,
:contour,:contour3d,:surface,:wireframe,
:heatmap, :pie, :image
))
end
# ----------------------------------------------------------------------
+65
View File
@@ -0,0 +1,65 @@
const _invisible = RGBA(0,0,0,0)
const _themes = KW(
:default => KW(
:bg => :white,
:bglegend => :match,
:bginside => :match,
:bgoutside => :match,
:fg => :auto,
:fglegend => :match,
:fggrid => :match,
:fgaxis => :match,
:fgtext => :match,
:fgborder => :match,
:fgguide => :match,
)
)
function add_theme(sym::Symbol, theme::KW)
_themes[sym] = theme
end
# add a new theme, using an existing theme as the base
function add_theme(sym::Symbol;
base = :default, # start with this theme
bg = _themes[base][:bg],
bglegend = _themes[base][:bglegend],
bginside = _themes[base][:bginside],
bgoutside = _themes[base][:bgoutside],
fg = _themes[base][:fg],
fglegend = _themes[base][:fglegend],
fggrid = _themes[base][:fggrid],
fgaxis = _themes[base][:fgaxis],
fgtext = _themes[base][:fgtext],
fgborder = _themes[base][:fgborder],
fgguide = _themes[base][:fgguide],
kw...)
_themes[sym] = merge(KW(
:bg => bg,
:bglegend => bglegend,
:bginside => bginside,
:bgoutside => bgoutside,
:fg => fg,
:fglegend => fglegend,
:fggrid => fggrid,
:fgaxis => fgaxis,
:fgtext => fgtext,
:fgborder => fgborder,
:fgguide => fgguide,
), KW(kw))
end
add_theme(:ggplot2,
bglegend = _invisible,
bginside = :lightgray,
fg = :white,
fglegend = _invisible,
fgtext = :gray,
fgguide = :black
)
function set_theme(sym::Symbol)
default(; _themes[sym]...)
end
+76 -29
View File
@@ -1,47 +1,94 @@
# TODO: I declare lots of types here because of the lacking ability to do forward declarations in current Julia
# I should move these to the relevant files when something like "extern" is implemented
typealias AVec AbstractVector
typealias AMat AbstractMatrix
typealias KW Dict{Symbol,Any}
immutable PlotsDisplay <: Display end
abstract AbstractBackend
abstract AbstractPlot{T<:AbstractBackend}
abstract AbstractLayout
typealias KW Dict{Symbol,Any}
# -----------------------------------------------------------
# Plot
immutable InputWrapper{T}
obj::T
end
wrap{T}(obj::T) = InputWrapper{T}(obj)
Base.isempty(wrapper::InputWrapper) = false
# -----------------------------------------------------------
# a single subplot
type Subplot{T<:AbstractBackend} <: AbstractLayout
parent::AbstractLayout
minpad::Tuple # leftpad, toppad, rightpad, bottompad
bbox::BoundingBox # the canvas area which is available to this subplot
plotarea::BoundingBox # the part where the data goes
attr::KW # args specific to this subplot
o # can store backend-specific data... like a pyplot ax
plt # the enclosing Plot object (can't give it a type because of no forward declarations)
end
# -----------------------------------------------------------
# simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place
type Axis
sp::Subplot
d::KW
end
type Extrema
emin::Float64
emax::Float64
end
Extrema() = Extrema(Inf, -Inf)
# -----------------------------------------------------------
typealias SubplotMap Dict{Any, Subplot}
# -----------------------------------------------------------
type Series
d::KW
end
attr(series::Series, k::Symbol) = series.d[k]
attr!(series::Series, v, k::Symbol) = (series.d[k] = v)
# -----------------------------------------------------------
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
backend::T # the backend type
n::Int # number of series
attr::KW # arguments for the whole plot
user_attr::KW # raw arg inputs (after aliases). these are used as the input dict in `_plot!`
series_list::Vector{Series} # arguments for each series
o # the backend's plot object
subplots::Vector{Subplot}
spmap::SubplotMap # provide any label as a map to a subplot
layout::AbstractLayout
inset_subplots::Vector{Subplot} # list of inset subplots
init::Bool
end
# -----------------------------------------------------------
# Layout
# -----------------------------------------------------------
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
function Plot()
Plot(backend(), 0, KW(), KW(), Series[], nothing,
Subplot[], SubplotMap(), EmptyLayout(),
Subplot[], false)
end
# TODO: make a decision... should plt[1] return the first subplot or the first series??
# Base.getindex(plt::Plot, i::Integer) = plt.subplots[i]
Base.getindex(plt::Plot, s::Symbol) = plt.spmap[s]
Base.getindex(plt::Plot, r::Integer, c::Integer) = plt.layout[r,c]
attr(plt::Plot, k::Symbol) = plt.attr[k]
attr!(plt::Plot, v, k::Symbol) = (plt.attr[k] = v)
# -----------------------------------------------------------------------
+299 -115
View File
@@ -22,10 +22,10 @@ function histogramHack(; 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[:seriestype] = :bar
d[:fillrange] = d[:fillrange] == nothing ? 0.0 : d[:fillrange]
d
end
@@ -64,7 +64,7 @@ function barHack(; kw...)
d[:x] = x
d[:y] = y
d[:linetype] = :path
d[:seriestype] = :path
d[:fillrange] = fillrange
d
end
@@ -94,12 +94,12 @@ function sticksHack(; kw...)
# change the line args
dLine[:x] = x
dLine[:y] = y
dLine[:linetype] = :path
dLine[:seriestype] = :path
dLine[:markershape] = :none
dLine[:fillrange] = nothing
# change the scatter args
dScatter[:linetype] = :none
dScatter[:seriestype] = :none
dLine, dScatter
end
@@ -114,14 +114,37 @@ function regressionXY(x, y)
regx, regy
end
function replace_image_with_heatmap{T<:Colorant}(z::Array{T})
@show T, size(z)
n, m = size(z)
# idx = 0
colors = ColorGradient(vec(z))
newz = reshape(linspace(0, 1, n*m), n, m)
newz, colors
# newz = zeros(n, m)
# for i=1:n, j=1:m
# push!(colors, T(z[i,j]...))
# newz[i,j] = idx / (n*m-1)
# idx += 1
# end
# newz, ColorGradient(colors)
end
function imageHack(d::KW)
:heatmap in supported_types() || error("Neither :image or :heatmap are supported!")
d[:seriestype] = :heatmap
d[:z], d[:fillcolor] = replace_image_with_heatmap(d[:z].surf)
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
Base.cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))]
Base.cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))]
Base.cycle(v, idx::Int) = v
makevec(v::AVec) = v
makevec{T}(v::T) = T[v]
@@ -133,13 +156,19 @@ 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{X,Y}(xy::AVec{Tuple{X,Y}}) = [t[1] for t in xy], [t[2] for t in xy]
unzip{X,Y,Z}(xyz::AVec{Tuple{X,Y,Z}}) = [t[1] for t in xyz], [t[2] for t in xyz], [t[3] for t in xyz]
unzip{X,Y,U,V}(xyuv::AVec{Tuple{X,Y,U,V}}) = [t[1] for t in xyuv], [t[2] for t in xyuv], [t[3] for t in xyuv], [t[4] for t in xyuv]
unzip{T}(xy::AVec{FixedSizeArrays.Vec{2,T}}) = T[t[1] for t in xy], T[t[2] for t in xy]
unzip{T}(xy::FixedSizeArrays.Vec{2,T}) = T[xy[1]], T[xy[2]]
unzip{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::AVec{FixedSizeArrays.Vec{3,T}}) = T[t[1] for t in xyz], T[t[2] for t in xyz], T[t[3] for t in xyz]
unzip{T}(xyz::FixedSizeArrays.Vec{3,T}) = T[xyz[1]], T[xyz[2]], T[xyz[3]]
unzip{T}(xyuv::AVec{FixedSizeArrays.Vec{4,T}}) = T[t[1] for t in xyuv], T[t[2] for t in xyuv], T[t[3] for t in xyuv], T[t[4] for t in xyuv]
unzip{T}(xyuv::FixedSizeArrays.Vec{4,T}) = T[xyuv[1]], T[xyuv[2]], T[xyuv[3]], T[xyuv[4]]
# 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
@@ -170,13 +199,20 @@ function replaceType(vec, val)
push!(vec, val)
end
function replaceAlias!(d::KW, k::Symbol, aliases::KW)
if haskey(aliases, k)
d[aliases[k]] = pop!(d, k)
end
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
replaceAlias!(d, k, aliases)
# if haskey(aliases, k)
# d[aliases[k]] = d[k]
# delete!(d, k)
# end
end
end
@@ -196,10 +232,31 @@ function heatmap_edges(v::AVec)
end
function calc_r_extrema(x, y)
xmin, xmax = extrema(x)
ymin, ymax = extrema(y)
r = 0.5 * min(xmax - xmin, ymax - ymin)
extrema(r)
end
function convert_to_polar(x, y, r_extrema = calc_r_extrema(x, y))
rmin, rmax = r_extrema
phi, r = x, y
r = 0.5 * (r - rmin) / (rmax - rmin)
n = max(length(phi), length(r))
x = zeros(n)
y = zeros(n)
for i in 1:n
x[i] = cycle(r,i) * cos(cycle(phi,i))
y[i] = cycle(r,i) * sin(cycle(phi,i))
end
x, y
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))'
y[r,:] = 0.95 * vec(y[r-1,:]) + randn(size(y,2))
end
y
end
@@ -207,6 +264,14 @@ end
isijulia() = isdefined(Main, :IJulia) && Main.IJulia.inited
isatom() = isdefined(Main, :Atom) && Main.Atom.isconnected()
function is_installed(pkgstr::AbstractString)
try
Pkg.installed(pkgstr) === nothing ? false: true
catch
false
end
end
istuple(::Tuple) = true
istuple(::Any) = false
isvector(::AVec) = true
@@ -216,8 +281,11 @@ ismatrix(::Any) = false
isscalar(::Real) = true
isscalar(::Any) = false
is_2tuple(v) = typeof(v) <: Tuple && length(v) == 2
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
@@ -229,12 +297,84 @@ 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))
function nansplit(v::AVec)
vs = Vector{eltype(v)}[]
while true
idx = findfirst(isnan, v)
if idx <= 0
# no nans
push!(vs, v)
break
elseif idx > 1
push!(vs, v[1:idx-1])
end
v = v[idx+1:end]
end
vs
end
function nanvcat(vs::AVec)
v_out = zeros(0)
for v in vs
nanappend!(v_out, v)
end
v_out
end
# 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
# this is a helper function to determine whether we need to transpose a surface matrix.
# it depends on whether the backend matches rows to x (transpose_on_match == true) or vice versa
# for example: PyPlot sends rows to y, so transpose_on_match should be true
function transpose_z(d::KW, z, transpose_on_match::Bool = true)
if d[:match_dimensions] == transpose_on_match
z'
else
z
end
end
function ok(x::Number, y::Number, z::Number = 0)
isfinite(x) && isfinite(y) && isfinite(z)
end
ok(tup::Tuple) = ok(tup...)
# compute one side of a fill range from a ribbon
function make_fillrange_side(y, rib)
frs = zeros(length(y))
for (i, (yi, ri)) in enumerate(zip(y, cycle(rib)))
frs[i] = yi + ri
end
frs
end
# turn a ribbon into a fillrange
function make_fillrange_from_ribbon(kw::KW)
y, rib = kw[:y], kw[:ribbon]
rib = wraptuple(rib)
rib1, rib2 = -first(rib), last(rib)
kw[:ribbon] = nothing
kw[:fillrange] = make_fillrange_side(y, rib1), make_fillrange_side(y, rib2)
end
# ---------------------------------------------------------------
wraptuple(x::@compat(Tuple)) = x
@@ -253,12 +393,13 @@ 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
with(:gadfly, size=(400,400), type=:histogram) do
plot(rand(10))
plot(rand(10))
end
@@ -297,7 +438,7 @@ function with(f::Function, args...; kw...)
# # 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
# k = :seriestype
# if arg in _allTypes
# olddefs[k] = default(k)
# newdefs[k] = arg
@@ -340,6 +481,7 @@ function with(f::Function, args...; kw...)
ret
end
# ---------------------------------------------------------------
# ---------------------------------------------------------------
type DebugMode
@@ -367,6 +509,7 @@ function dumpdict(d::KW, prefix = "", alwaysshow = false)
end
println()
end
DD(d::KW, prefix = "") = dumpdict(d, prefix, true)
function dumpcallstack()
@@ -374,70 +517,7 @@ function dumpcallstack()
end
# ---------------------------------------------------------------
# push/append/clear/set the underlying plot data
# NOTE: backends should implement the getindex and setindex! methods to get/set the x/y data objects
# index versions
function Base.push!(plt::Plot, i::Integer, x::Real, y::Real)
xdata, ydata = plt[i]
plt[i] = (extendSeriesData(xdata, x), extendSeriesData(ydata, y))
plt
end
function Base.push!(plt::Plot, i::Integer, y::Real)
xdata, ydata = plt[i]
# if !isa(xdata, UnitRange)
# error("Expected x is a UnitRange since you're trying to push a y value only. typeof(x) = $(typeof(xdata))")
# end
plt[i] = (extendSeriesByOne(xdata), extendSeriesData(ydata, y))
plt
end
Base.push!(plt::Plot, y::Real) = push!(plt, 1, y)
# update all at once
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
function Base.push!(plt::Plot, x::Real, y::AVec)
push!(plt, [x], y)
end
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
# append to index
function Base.append!(plt::Plot, i::Integer, x::AVec, y::AVec)
@assert length(x) == length(y)
xdata, ydata = plt[i]
plt[i] = (extendSeriesData(xdata, x), extendSeriesData(ydata, y))
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
# ---------------------------------------------------------------
# used in updating an existing series
extendSeriesByOne(v::UnitRange{Int}, n::Int = 1) = isempty(v) ? (1:n) : (minimum(v):maximum(v)+n)
@@ -448,40 +528,144 @@ 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
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)
tovec(v::AbstractVector) = v
tovec(v::Void) = zeros(0)
function getxy(plt::Plot, i::Integer)
d = plt.series_list[i].d
tovec(d[:x]), tovec(d[:y])
end
function getxyz(plt::Plot, i::Integer)
d = plt.series_list[i].d
tovec(d[:x]), tovec(d[:y]), tovec(d[:z])
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
function setxy!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer)
series = plt.series_list[i]
series.d[:x], series.d[:y] = xy
sp = series.d[:subplot]
expand_extrema!(sp.attr[:xaxis], xy[1])
expand_extrema!(sp.attr[:yaxis], xy[2])
_series_updated(plt, series)
end
function setxyz!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer)
series = plt.series_list[i]
series.d[:x], series.d[:y], series.d[:z] = xyz
sp = series.d[:subplot]
expand_extrema!(sp.attr[:xaxis], xyz[1])
expand_extrema!(sp.attr[:yaxis], xyz[2])
expand_extrema!(sp.attr[:zaxis], xyz[3])
_series_updated(plt, series)
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
# ---------------------------------------------------------------
@@ -503,9 +687,9 @@ mm2px(mm::Real) = float(px / MM_PER_PX)
"Smallest x in plot"
xmin(plt::Plot) = minimum([minimum(d[:x]) for d in plt.seriesargs])
xmin(plt::Plot) = minimum([minimum(series.d[:x]) for series in plt.series_list])
"Largest x in plot"
xmax(plt::Plot) = maximum([maximum(d[:x]) for d in plt.seriesargs])
xmax(plt::Plot) = maximum([maximum(series.d[:x]) for series in plt.series_list])
"Extrema of x-values in plot"
Base.extrema(plt::Plot) = (xmin(plt), xmax(plt))
+5 -4
View File
@@ -1,15 +1,16 @@
julia 0.4
RecipesBase
Colors
Reexport
Requires
Measures
FactCheck
Cairo
Gadfly
Images
ImageMagick
PyPlot
@osx QuartzImageIO
GR
DataFrames
RDatasets
VisualRegressionTests
UnicodePlots
Glob
+59 -26
View File
@@ -1,6 +1,6 @@
using VisualRegressionTests
using ExamplePlots
# using ExamplePlots
import DataFrames, RDatasets
@@ -14,6 +14,7 @@ end
using Plots, FactCheck
using Glob
default(size=(500,300))
@@ -21,37 +22,69 @@ default(size=(500,300))
# TODO: use julia's Condition type and the wait() and notify() functions to initialize a Window, then wait() on a condition that
# is referenced in a button press callback (the button clicked callback will call notify() on that condition)
const _current_plots_version = v"0.7.1"
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2)
Plots._debugMode.on = debug
example = Plots._examples[idx]
info("Testing plot: $pkg:$idx:$(example.header)")
backend(pkg)
backend()
# first
Plots._debugMode.on = debug
example = ExamplePlots._examples[idx]
info("Testing plot: $pkg:$idx:$(example.header)")
backend(pkg)
backend()
# ensure consistent results
srand(1234)
# ensure consistent results
srand(1234)
# reference image directory setup
# refdir = joinpath(Pkg.dir("ExamplePlots"), "test", "refimg", string(pkg))
refdir = Pkg.dir("PlotReferenceImages", "Plots", string(pkg))
fn = "ref$idx.png"
# reference image directory setup
refdir = joinpath(Pkg.dir("ExamplePlots"), "test", "refimg", string(pkg))
# firgure out version info
G = glob(relpath(refdir) * "/*")
# @show refdir fn G
versions = map(fn -> VersionNumber(split(fn,"/")[end]), G)
versions = reverse(sort(versions))
versions = filter(v -> v <= _current_plots_version, versions)
# @show refdir fn versions
# test function
func = (fn, idx) -> begin
map(eval, example.exprs)
png(fn)
end
newdir = joinpath(refdir, string(_current_plots_version))
newfn = joinpath(newdir, fn)
try
run(`mkdir -p $refdir`)
catch err
display(err)
end
reffn = joinpath(refdir, "ref$idx.png")
# figure out which reference file we should compare to, by finding the highest versioned file
reffn = nothing
for v in versions
tmpfn = joinpath(refdir, string(v), fn)
if isfile(tmpfn)
reffn = tmpfn
break
end
end
# the test
vtest = VisualTest(func, reffn, idx)
test_images(vtest, popup=popup, sigma=sigma, eps=eps)
# now we have the fn (if any)... do the comparison
# @show reffn
if reffn == nothing
reffn = newfn
end
# @show reffn
# return
# test function
func = (fn, idx) -> begin
map(eval, example.exprs)
png(fn)
end
try
run(`mkdir -p $newdir`)
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, newfn = newfn)
end
function image_comparison_facts(pkg::Symbol;
@@ -60,7 +93,7 @@ function image_comparison_facts(pkg::Symbol;
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)
for i in 1:length(Plots._examples)
i in skip && continue
if only == nothing || i in only
@fact image_comparison_tests(pkg, i, debug=debug, sigma=sigma, eps=eps) |> success --> true
+22
View File
@@ -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
+68 -14
View File
@@ -4,34 +4,88 @@ include("imgcomp.jl")
# don't actually show the plots
srand(1234)
default(show=false)
default(show=false, reuse=true)
img_eps = 5e-2
facts("Gadfly") do
@fact gadfly() --> Plots.GadflyBackend()
@fact backend() --> Plots.GadflyBackend()
@fact typeof(plot(1:10)) --> Plots.Plot{Plots.GadflyBackend}
@fact plot(Int[1,2,3], rand(3)) --> not(nothing)
@fact plot(sort(rand(10)), rand(Int, 10, 3)) --> not(nothing)
@fact plot!(rand(10,3), rand(10,3)) --> not(nothing)
image_comparison_facts(:gadfly, skip=[4,6,19,23,24], eps=img_eps)
end
# facts("Gadfly") do
# @fact gadfly() --> Plots.GadflyBackend()
# @fact backend() --> Plots.GadflyBackend()
#
# @fact typeof(plot(1:10)) --> Plots.Plot{Plots.GadflyBackend}
# @fact plot(Int[1,2,3], rand(3)) --> not(nothing)
# @fact plot(sort(rand(10)), rand(Int, 10, 3)) --> not(nothing)
# @fact plot!(rand(10,3), rand(10,3)) --> not(nothing)
#
# image_comparison_facts(:gadfly, skip=[4,6,23,24,27], eps=img_eps)
# end
facts("PyPlot") do
@fact pyplot() --> Plots.PyPlotBackend()
@fact backend() --> Plots.PyPlotBackend()
image_comparison_facts(:pyplot, skip=[4,10,13,19,21,23], eps=img_eps)
image_comparison_facts(:pyplot, skip=[30], eps=img_eps)
end
facts("GR") do
@fact gr() --> Plots.GRBackend()
@fact backend() --> Plots.GRBackend()
# image_comparison_facts(:gr, only=[1], eps=img_eps)
@linux_only image_comparison_facts(:gr, skip=[30], eps=img_eps)
end
facts("Plotly") do
@fact plotly() --> Plots.PlotlyBackend()
@fact backend() --> Plots.PlotlyBackend()
# # until png generation is reliable on OSX, just test on linux
# @linux_only image_comparison_facts(:plotly, only=[1,3,4,7,8,9,10,11,12,14,15,20,22,23,27], eps=img_eps)
end
# 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
facts("Axes") do
p = plot()
axis = p.subplots[1][:xaxis]
@fact typeof(axis) --> Axis
@fact Plots.discrete_value!(axis, "HI") --> (0.5, 1)
@fact Plots.discrete_value!(axis, :yo) --> (1.5, 2)
@fact extrema(axis) --> (0.5,1.5)
@fact axis[:discrete_map] --> Dict{Any,Any}(:yo => 2, "HI" => 1)
Plots.discrete_value!(axis, ["x$i" for i=1:5])
Plots.discrete_value!(axis, ["x$i" for i=0:2])
@fact extrema(axis) --> (0.5, 7.5)
end
FactCheck.exitstatus()
end # module
+27
View File
@@ -0,0 +1,27 @@
# Pkg.clone(pwd())
# Pkg.build("Plots")
Pkg.clone("ImageMagick")
Pkg.build("ImageMagick")
Pkg.clone("GR")
Pkg.build("GR")
# Pkg.clone("https://github.com/JuliaPlots/RecipesBase.jl.git")
# Pkg.clone("https://github.com/tbreloff/ExamplePlots.jl.git");
Pkg.clone("https://github.com/JuliaPlots/PlotReferenceImages.jl.git")
# Pkg.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")
Pkg.checkout("RecipesBase")
Pkg.clone("VisualRegressionTests")
ENV["PYTHON"] = ""
Pkg.add("PyPlot")
Pkg.build("PyPlot")
Pkg.test("Plots"; coverage=false)