diff --git a/src/arg_desc.jl b/src/arg_desc.jl index ce24b8e0..f0df3e92 100644 --- a/src/arg_desc.jl +++ b/src/arg_desc.jl @@ -94,6 +94,7 @@ const _arg_desc = KW( :subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.", :colorbar_title => "String. Title of colorbar.", :framestyle => "Symbol. Style of the axes frame. Choose from $(_allFramestyles)", +:camera => "NTuple{2, Real}. Sets the view angle (azimuthal, elevation) for 3D plots", # axis args :guide => "String. Axis guide (label).", diff --git a/src/args.jl b/src/args.jl index 3ea3efde..7632b5bf 100644 --- a/src/args.jl +++ b/src/args.jl @@ -316,6 +316,7 @@ const _subplot_defaults = KW( :subplot_index => -1, :colorbar_title => "", :framestyle => :axes, + :camera => (30,30), ) const _axis_defaults = KW( @@ -532,6 +533,7 @@ add_aliases(:gridlinewidth, :gridwidth, :grid_linewidth, :grid_width, :gridlw, : add_aliases(:gridstyle, :grid_style, :gridlinestyle, :grid_linestyle, :grid_ls, :gridls) add_aliases(:framestyle, :frame_style, :frame, :axesstyle, :axes_style, :boxstyle, :box_style, :box, :borderstyle, :border_style, :border) add_aliases(:tick_direction, :tickdirection, :tick_dir, :tickdir, :tick_orientation, :tickorientation, :tick_or, :tickor) +add_aliases(:camera, :cam, :viewangle, :view_angle) # add all pluralized forms to the _keyAliases dict for arg in keys(_series_defaults) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 056436f2..9d9d6b32 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -34,6 +34,7 @@ const _gr_attr = merge_with_base_supported([ :arrow, :framestyle, :tick_direction, + :camera, ]) const _gr_seriestype = [ :path, :scatter, @@ -741,7 +742,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) isfinite(clims[1]) && (zmin = clims[1]) isfinite(clims[2]) && (zmax = clims[2]) end - GR.setspace(zmin, zmax, 35, 60) + GR.setspace(zmin, zmax, round.(Int, sp[:camera])...) xtick = GR.tick(xmin, xmax) / 2 ytick = GR.tick(ymin, ymax) / 2 ztick = GR.tick(zmin, zmax) / 2 diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index b54d8aa0..a5f1a1b0 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -33,6 +33,7 @@ const _pgfplots_attr = merge_with_base_supported([ # :match_dimensions, :tick_direction, :framestyle, + :camera, ]) const _pgfplots_seriestype = [:path, :path3d, :scatter, :steppre, :stepmid, :steppost, :histogram2d, :ysticks, :xsticks, :contour, :shape] const _pgfplots_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] @@ -380,6 +381,11 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) kw[:legendPos] = _pgfplots_legend_pos[legpos] end + if is3d(sp) + azim, elev = sp[:camera] + kw[:view] = "{$(azim)}{$(elev)}" + end + axisf = PGFPlots.Axis if sp[:projection] == :polar axisf = PGFPlots.PolarAxis diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index b3c40158..c67511d2 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -35,6 +35,7 @@ const _plotly_attr = merge_with_base_supported([ :clims, :framestyle, :tick_direction, + :camera, ]) const _plotly_seriestype = [ @@ -324,10 +325,21 @@ function plotly_layout(plt::Plot) # if any(is3d, seriesargs) if is3d(sp) + azim = sp[:camera][1] - 90 #convert azimuthal to match GR behaviour + theta = 90 - sp[:camera][2] #spherical coordinate angle from z axis 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), + + #2.6 multiplier set camera eye such that whole plot can be seen + :camera => KW( + :eye => KW( + :x => cosd(azim)*sind(theta)*2.6, + :y => sind(azim)*sind(theta)*2.6, + :z => cosd(theta)*2.6, + ), + ), ) else d_out[Symbol("xaxis$spidx")] = plotly_axis(sp[:xaxis], sp) diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 6ad195a2..e3b70eb9 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -35,6 +35,7 @@ const _pyplot_attr = merge_with_base_supported([ :stride, :framestyle, :tick_direction, + :camera, ]) const _pyplot_seriestype = [ :path, :steppre, :steppost, :shape, @@ -1104,6 +1105,13 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) ax[:set_aspect](isa(aratio, Symbol) ? string(aratio) : aratio, anchor = "C") end + #camera/view angle + if is3d(sp) + #convert azimuthal to match GR behaviour + #view_init(elevation, azimuthal) so reverse :camera args + ax[:view_init]((sp[:camera].-(90,0))[end:-1:1]...) + end + # legend py_add_legend(plt, sp, ax)