Skip to content

Commit 518f84e

Browse files
committed
add Segment plot
1 parent 4a3365c commit 518f84e

File tree

3 files changed

+252
-0
lines changed

3 files changed

+252
-0
lines changed

src/StatisticalGraphics.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module StatisticalGraphics
1111
SGMarks,
1212
Bar,
1313
Band,
14+
Segment,
1415
Scatter,
1516
Line,
1617
Histogram,
@@ -49,6 +50,7 @@ module StatisticalGraphics
4950
include("charts/scatter.jl")
5051
include("charts/line.jl")
5152
include("charts/band.jl")
53+
include("charts/segment.jl")
5254
include("charts/histogram.jl")
5355
include("charts/hist2d.jl")
5456
include("charts/density.jl")

src/charts/segment.jl

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
"""
2+
Segment(args...)
3+
4+
Represent a Segment line with given arguments.
5+
6+
$(print_doc(SEGMENT_DEFAULT))
7+
"""
8+
mutable struct Segment <: SGMarks
9+
opts
10+
function Segment(;opts...)
11+
optsd = val_opts(opts)
12+
cp_SEGMENT_DEFAULT = update_default_opts!(deepcopy(SEGMENT_DEFAULT), optsd)
13+
if (cp_SEGMENT_DEFAULT[:x] == 0 && cp_SEGMENT_DEFAULT[:y] == 0) || ((cp_SEGMENT_DEFAULT[:lower] isa Integer && cp_SEGMENT_DEFAULT[:lower] == 0) || (cp_SEGMENT_DEFAULT[:upper] isa Integer && cp_SEGMENT_DEFAULT[:upper] == 0)) || (cp_SEGMENT_DEFAULT[:lower] isa Float64 && cp_SEGMENT_DEFAULT[:upper] isa Float64)
14+
throw(ArgumentError("Segment plot needs one of x and y keyword arguments and both lower and upper keyword arguments"))
15+
end
16+
new(cp_SEGMENT_DEFAULT)
17+
end
18+
end
19+
20+
# Segment graphic produce a simple 2D Segment line
21+
# It requires three keyword arguments; one of x and y + lower and upper
22+
# It needs the input data set to be passed dirctly to vega
23+
function _push_plots!(vspec,plt::Segment, all_args; idx=1)
24+
# check if the required arguments are passed
25+
_check_and_normalize!(plt, all_args)
26+
_add_legends!(plt, all_args, idx)
27+
opts = plt.opts
28+
29+
s_spec = Dict{Symbol,Any}()
30+
s_spec[:type] = "group"
31+
s_spec[:clip] = something(opts[:clip], all_args.opts[:clip])
32+
s_spec_marks = Dict{Symbol,Any}()
33+
s_spec_marks[:type] = "rect"
34+
s_spec_marks[:from] = Dict(:data => "source_0_$idx")
35+
s_spec_marks[:encode] = Dict{Symbol,Any}()
36+
s_spec_marks[:encode][:enter] = Dict{Symbol,Any}()
37+
s_spec_marks[:encode][:enter][:opacity] = Dict(:value => opts[:opacity])
38+
s_spec_marks[:encode][:enter][:strokeWidth] = Dict(:value => opts[:thickness])
39+
40+
s_spec_marks[:encode][:enter][:stroke] = Dict{Symbol,Any}()
41+
42+
# group in all plots uses the same scale
43+
if opts[:group] === nothing
44+
s_spec_marks[:encode][:enter][:stroke][:value] = opts[:color]
45+
else
46+
s_spec[:from] = Dict{Symbol,Any}()
47+
s_spec[:from][:facet] = Dict{Symbol,Any}()
48+
s_spec[:from][:facet][:name] = "group_facet_source"
49+
s_spec[:from][:facet][:data] = "source_0_$idx"
50+
s_spec[:from][:facet][:groupby] = opts[:group]
51+
s_spec_marks[:from][:data] = "group_facet_source"
52+
s_spec_marks[:encode][:enter][:stroke][:scale] = "group_scale"
53+
s_spec_marks[:encode][:enter][:stroke][:field] = opts[:group]
54+
# group is the 5th element of scales
55+
addto_group_scale!(vspec[:scales][5], "source_0_$idx", opts[:group], all_args)
56+
end
57+
if opts[:x] != 0
58+
# vertical
59+
s_spec_marks[:encode][:enter][:x] = Dict{Symbol,Any}()
60+
if opts[:x2axis]
61+
s_spec_marks[:encode][:enter][:x][:scale] = "x2"
62+
addto_scale!(all_args, 2, all_args.ds, opts[:x])
63+
addto_axis!(vspec[:axes][2], all_args.axes[2], opts[:x])
64+
else
65+
s_spec_marks[:encode][:enter][:x][:scale] = "x1"
66+
addto_scale!(all_args, 1, all_args.ds, opts[:x])
67+
addto_axis!(vspec[:axes][1], all_args.axes[1], opts[:x])
68+
end
69+
s_spec_marks[:encode][:enter][:x][:field] = opts[:x]
70+
s_spec_marks[:encode][:enter][:y] = Dict{Symbol,Any}()
71+
s_spec_marks[:encode][:enter][:y2] = Dict{Symbol,Any}()
72+
73+
l_val = false
74+
u_val = false
75+
if opts[:lower] isa Float64
76+
lower_value = opts[:lower]
77+
opts[:lower] = opts[:upper]
78+
l_val = true
79+
end
80+
if opts[:upper] isa Float64
81+
upper_value = opts[:upper]
82+
opts[:upper] = opts[:lower]
83+
u_val = true
84+
end
85+
86+
if opts[:y2axis]
87+
s_spec_marks[:encode][:enter][:y][:scale] = "y2"
88+
addto_scale!(all_args, 4, all_args.ds, opts[:lower])
89+
addto_axis!(vspec[:axes][4], all_args.axes[4], opts[:lower])
90+
s_spec_marks[:encode][:enter][:y2][:scale] = "y2"
91+
addto_scale!(all_args, 4, all_args.ds, opts[:upper])
92+
else
93+
s_spec_marks[:encode][:enter][:y][:scale] = "y1"
94+
addto_scale!(all_args, 3, all_args.ds, opts[:lower])
95+
addto_axis!(vspec[:axes][3], all_args.axes[3], opts[:lower])
96+
s_spec_marks[:encode][:enter][:y2][:scale] = "y1"
97+
addto_scale!(all_args, 3, all_args.ds, opts[:upper])
98+
end
99+
l_val ? s_spec_marks[:encode][:enter][:y][:value] = lower_value : s_spec_marks[:encode][:enter][:y][:field] = opts[:lower]
100+
u_val ? s_spec_marks[:encode][:enter][:y2][:value] = upper_value : s_spec_marks[:encode][:enter][:y2][:field] = opts[:upper]
101+
102+
else
103+
# horizontal
104+
s_spec_marks[:encode][:enter][:y] = Dict{Symbol,Any}()
105+
if opts[:y2axis]
106+
s_spec_marks[:encode][:enter][:y][:scale] = "y2"
107+
addto_scale!(all_args, 4, all_args.ds, opts[:y])
108+
addto_axis!(vspec[:axes][4], all_args.axes[4], opts[:y])
109+
else
110+
s_spec_marks[:encode][:enter][:y][:scale] = "y1"
111+
addto_scale!(all_args, 3, all_args.ds, opts[:y])
112+
addto_axis!(vspec[:axes][3], all_args.axes[3], opts[:y])
113+
end
114+
s_spec_marks[:encode][:enter][:y][:field] = opts[:y]
115+
s_spec_marks[:encode][:enter][:x] = Dict{Symbol,Any}()
116+
s_spec_marks[:encode][:enter][:x2] = Dict{Symbol,Any}()
117+
l_val = false
118+
u_val = false
119+
if opts[:lower] isa Float64
120+
lower_value = opts[:lower]
121+
opts[:lower] = opts[:upper]
122+
l_val = true
123+
end
124+
if opts[:upper] isa Float64
125+
upper_value = opts[:upper]
126+
opts[:upper] = opts[:lower]
127+
u_val = true
128+
end
129+
130+
if opts[:x2axis]
131+
s_spec_marks[:encode][:enter][:x][:scale] = "x2"
132+
addto_scale!(all_args, 2, all_args.ds, opts[:lower])
133+
addto_axis!(vspec[:axes][2], all_args.axes[2], opts[:lower])
134+
s_spec_marks[:encode][:enter][:x2][:scale] = "x2"
135+
addto_scale!(all_args, 2, all_args.ds, opts[:upper])
136+
else
137+
s_spec_marks[:encode][:enter][:x][:scale] = "x1"
138+
addto_scale!(all_args, 1, all_args.ds, opts[:lower])
139+
addto_axis!(vspec[:axes][1], all_args.axes[1], opts[:lower])
140+
s_spec_marks[:encode][:enter][:x2][:scale] = "x1"
141+
addto_scale!(all_args, 1, all_args.ds, opts[:upper])
142+
end
143+
l_val ? s_spec_marks[:encode][:enter][:x][:value] = lower_value : s_spec_marks[:encode][:enter][:x][:field] = opts[:lower]
144+
u_val ? s_spec_marks[:encode][:enter][:x2][:value] = upper_value : s_spec_marks[:encode][:enter][:x2][:field] = opts[:upper]
145+
end
146+
filter_data = Dict{Symbol,Any}()
147+
filter_data[:name] = "source_0_$idx"
148+
filter_data[:source] = "source_0"
149+
filter_data[:transform] = [Dict{Symbol,Any}(:type => :filter, :expr => "(isValid(datum['$(opts[:x])']) || isValid(datum['$(opts[:y])'])) && isValid(datum['$(opts[:lower])']) && isValid(datum['$(opts[:upper])'])")]
150+
151+
push!(vspec[:data], filter_data)
152+
s_spec[:marks] = [s_spec_marks]
153+
push!(vspec[:marks], s_spec)
154+
end
155+
156+
157+
# converts all column names to string, also check if the required arguments are passed
158+
# TODO use macro to generate repeated code
159+
function _check_and_normalize!(plt::Segment, all_args)
160+
opts = plt.opts
161+
ds = all_args.ds
162+
cols = all_args.referred_cols
163+
if opts[:x] != 0 && length(IMD.index(ds)[opts[:x]]) == 1
164+
append!(cols, IMD.index(ds)[opts[:x]])
165+
opts[:x] = _colname_as_string(ds, opts[:x])
166+
elseif opts[:x] != 0
167+
@goto argerr
168+
end
169+
if opts[:y] != 0 && length(IMD.index(ds)[opts[:y]]) == 1
170+
append!(cols, IMD.index(ds)[opts[:y]])
171+
opts[:y] = _colname_as_string(ds, opts[:y])
172+
elseif opts[:y] != 0
173+
@goto argerr
174+
end
175+
if !isa(opts[:lower], Float64) && length(IMD.index(ds)[opts[:lower]]) == 1
176+
append!(cols, IMD.index(ds)[opts[:lower]])
177+
opts[:lower] = _colname_as_string(ds, opts[:lower])
178+
elseif !isa(opts[:lower], Float64)
179+
@goto argerr
180+
end
181+
if !isa(opts[:upper], Float64) && length(IMD.index(ds)[opts[:upper]]) == 1
182+
append!(cols, IMD.index(ds)[opts[:upper]])
183+
opts[:upper] = _colname_as_string(ds, opts[:upper])
184+
elseif !isa(opts[:upper], Float64)
185+
@goto argerr
186+
end
187+
if opts[:group] !== nothing
188+
if length(IMD.index(ds)[opts[:group]]) == 1
189+
append!(cols, IMD.index(ds)[opts[:group]])
190+
opts[:group] = _colname_as_string(ds, opts[:group])
191+
else
192+
@goto argerr
193+
end
194+
end
195+
return plt
196+
@label argerr
197+
throw(ArgumentError("only a single column must be selected"))
198+
end
199+
200+
201+
function _add_legends!(plt::Segment, all_args, idx)
202+
opts = plt.opts
203+
# find the suitable scales for the legend
204+
# group, color, symbol, angle, ...
205+
which_scale = [opts[:group]]
206+
207+
if opts[:legend] === nothing
208+
legend_id = "__internal__name__for__legend__$idx"
209+
else
210+
legend_id = opts[:legend]
211+
end
212+
if all_args.legends isa Vector
213+
loc_of_leg = findfirst(x->x.opts[:name] == legend_id, all_args.legends)
214+
else
215+
loc_of_leg = nothing
216+
end
217+
if loc_of_leg !== nothing # user provided some customisation
218+
leg_spec = all_args.legends[loc_of_leg]
219+
else
220+
leg_spec = Legend(name = legend_id)
221+
end
222+
223+
leg_spec_cp = Dict{Symbol, Any}()
224+
if which_scale[1] !== nothing
225+
_title = which_scale[1]
226+
leg_spec_cp[:fill] = "group_scale"
227+
_build_legen!(leg_spec_cp, leg_spec.opts, "square", _title, "$(legend_id)_group_scale_legend_$idx", all_args)
228+
push!(all_args.out_legends, leg_spec_cp)
229+
end
230+
end

src/kwds.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,26 @@ BAND_DEFAULT = SGKwds(
259259
:clip => __dic(:default=> nothing, :__ord=>7, :__cat=>"Miscellaneous", :__doc=>Kwds_docs[:clip]),
260260
)
261261

262+
SEGMENT_DEFAULT = SGKwds(
263+
:x => __dic(:default=> 0, :__ord=>0, :__cat => "Required", :__doc=>"The column to be used as x coordinate. User must pass either this or the `y` argument."),
264+
:y => __dic(:default=> 0, :__ord=>0, :__cat=> "Required", :__doc=>"The column to be used as y coordinate. User must pass either this or the `x` argument."),
265+
:lower => __dic(:default=> 0, :__ord=>0, :__cat=> "Required", :__doc=>"The lower values for the band plot. User can pass a column or a Float value."),
266+
:upper => __dic(:default=> 0, :__ord=>0, :__cat=> "Required", :__doc=>"The upper values for the band plot. User can pass a column or a Float value."),
267+
:group => __dic(:default=> nothing, :__ord=>2, :__cat=>"Grouping", :__doc=>"The name of column for grouping observation. Each group of observations will create seperate band plot."),
268+
269+
:x2axis => __dic(:default=> false, :__ord=>5, :__cat=>"Axes options", :__doc=>Kwds_docs[:x2axis]),
270+
:y2axis => __dic(:default=> false, :__ord=>5, :__cat=>"Axes options", :__doc=>Kwds_docs[:y2axis]),
271+
272+
:opacity => __dic(:default=> 1, :__ord=>3, :__cat=>"Band appearance", :__doc=>Kwds_docs[:opacity]),
273+
:color => __dic(:default=> "#4682b4", :__ord=>3, :__cat=>"Band appearance", :__doc=>Kwds_docs[:color_grad]),
274+
275+
:thickness => __dic(:default=> 1, :__ord=>1, :__cat=>"Band Options", :__doc=>"The thickness of the mark"),
276+
277+
:legend => __dic(:default=> nothing, :__ord=>6, :__cat=>"Legend", :__doc=>Kwds_docs[:legend]),
278+
279+
:clip => __dic(:default=> nothing, :__ord=>7, :__cat=>"Miscellaneous", :__doc=>Kwds_docs[:clip]),
280+
)
281+
262282
DENSITY_DEFAULT = SGKwds(
263283
:x => __dic(:default=> 0, :__ord=>0, :__cat => "Required", :__doc=>"The column to be used as x coordinate. User must pass either this or the `y` argument."),
264284
:y => __dic(:default=> 0, :__ord=>0, :__cat=> "Required", :__doc=>"The column to be used as y coordinate. User must pass either this or the `x` argument."),

0 commit comments

Comments
 (0)