Skip to content

Commit 932c968

Browse files
committed
organise keyword arguments + automatic docstring
1 parent 089505d commit 932c968

File tree

3 files changed

+170
-56
lines changed

3 files changed

+170
-56
lines changed

src/StatisticalGraphics.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ module StatisticalGraphics
4242

4343
include("util.jl")
4444
include("util_reg.jl")
45+
include("kwds.jl")
4546
include("gradient.jl")
4647
include("charts/axes.jl")
4748
include("charts/legend.jl")

src/charts/bar.jl

Lines changed: 7 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,10 @@
1-
BAR_DEFAULT = Dict{Symbol,Any}(:x => 0, :y => 0, :group => nothing,
2-
:response => nothing,
3-
:stat => nothing, #by default, if response is given we use sum, if not we use freq - the function passed as stat must accept two arguments f and x, f is a function and x is a abstract vector. function apply f on each elements of x and return the aggregations
4-
:normalize => false, # use normalizer for more control about how to do it
5-
:normalizer => x -> x ./ sum(x), # within each level of x/y
6-
:x2axis => false,
7-
:y2axis => false,
8-
:opacity => 1,
9-
:outlinethickness => 1,
10-
:barwidth => 1, # can be between[0,1], or :nest to display each group nested in the other one
11-
:nestfactor => nothing,
12-
:filled => true,
13-
:fill => "null",
14-
:fillcolor => :white,
15-
:opacity => 1,
16-
:color => "#4682b4",
17-
:colorresponse => nothing,
18-
:colorstat => nothing, # the same rule as :stat
19-
:colormodel => :diverging, # we use linear scale to produce colors
20-
:space => 0.1, # the space between bars - the space is calculated as space * total_bandwidth
21-
:groupspace => 0.05, # the space between bars inside each group - for groupdisplay = :cluster
22-
:outlinecolor => :white,
23-
:groupdisplay => :stack, #:stack, :cluster, :step (i.e. stacked and cluster), or :none
24-
:grouporder => :ascending, # :data, :ascending, :descending, userdefined order (by giving a vector of group level) - having a group column in panelby can cause some issues
25-
:orderresponse => nothing, # by default axis order control it, but it can be controlled by a column
26-
:orderstat => nothing, # freq is default aggregator, however, it can be any other function
27-
:baseline => 0,
28-
:baselineresponse => nothing, # each bar (or each group when groupped) can have its own baseline
29-
:baselinestat => nothing, # same rule as :stat
30-
31-
:missingmode => 0, # how to handle missings in category or group. 0 = nothing, 1 = no missing in category, 2 = no missing in group, 3 = no missing in category or group, 4 = no missing in both category and group
32-
33-
#data label
34-
:label=>:none, # :height or :category
35-
:labelfont=>nothing,
36-
:labelbaseline=>nothing,
37-
:labelfontweight=>nothing,
38-
:labelitalic=>nothing,
39-
:labelsize=>nothing,
40-
:labelcolor=>:black,# allow :group, :colorresponse to use their color if available
41-
:labelangle=>nothing,
42-
:labeldir=>:ltr,
43-
:labellimit=>nothing,
44-
:labeloffset=>0,
45-
:labelpos => :end, # :end, :start, :middle
46-
:labelloc=>0.5, # between 0 and 1
47-
:labeld3format=>"",
48-
:labelopacity=>1,
49-
:labelalign=>nothing,
50-
:labelalternate=>true, # if true, it automatically change the baseline, align and offset of the label text
51-
:tooltip => false, # it can be true, only if labelresponse is provided
52-
53-
54-
:legend => nothing, :barcorner => [0, 0, 0, 0], #corner radius (cornerRadiusTopLeft, cornerRadiusTopRight, cornerRadiusBottomLeft, cornerRadiusBottomRight)
55-
:clip => nothing
56-
)
1+
"""
2+
Bar(args...)
3+
4+
Represent a Bar chart with given arguments.
5+
6+
$(print_doc(BAR_DEFAULT))
7+
"""
578
mutable struct Bar <: SGMarks
589
opts
5910
function Bar(; opts...)

src/kwds.jl

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
const SG_current_theme = Ref{Symbol}(:default)
2+
3+
function set_theme!(th::Symbol)
4+
th in (:__ord, :__cat, :__doc) && throw(ArgumentError("The theme name is incorrect."))
5+
SG_current_theme[] = th
6+
@info "The default theme for StatisticalGraphics is changed to $(repr(th))."
7+
end
8+
9+
__dic = Dict{Symbol, Any}
10+
11+
# SGKwds will be used to specify the mark properties.
12+
# Every property must have the following keys: :default, :__ord, :__cat, :__doc
13+
# :default provides the instructions for default behaviour
14+
# :__ord provides the order of the property in the docstring
15+
# :__cat provides the information about the category that the property belongs to in docstring
16+
# :__doc is the actual text that will be used for the property's docstring
17+
# for other themes, we can add the default value of the property by passing it to the name of the theme, e.g. :dark=>:white,
18+
# which means mark should use `:white` as the default value if the theme is set to `:dark`
19+
struct SGKwds
20+
opts::__dic
21+
function SGKwds(args...)
22+
new(__dic(args...))
23+
end
24+
end
25+
26+
function Base.getindex(d::SGKwds, k)
27+
get(d.opts[:k], SG_current_theme[], :default)
28+
end
29+
30+
function Base.deepcopy(d::SGKwds)
31+
res=__dic()
32+
for (k, v) in d.opts
33+
push!(res, k => deepcopy(get(v, SG_current_theme[], v[:default])))
34+
end
35+
res
36+
end
37+
38+
getdoc(d::SGKwds, k) = get(d.opts[:k], :__doc, nothing)
39+
getcat(d::SGKwds, k) = get(d.opts[:k], :__cat, nothing)
40+
41+
42+
function print_doc(sgkwds::SGKwds)
43+
sgk=sgkwds.opts
44+
ds = Dataset(allk=Symbol[], dfs=Any[], ords=Int[], cat=String[], docs=String[])
45+
46+
# first we collect every thing into a dataset
47+
for (k, v) in sgk
48+
push!(ds, [k, v[:default], v[:__ord], v[:__cat], v[:__doc]])
49+
end
50+
# we use ords to find the order of categories, and
51+
# within each category we sort the values of keys
52+
sort!(ds, [:ords, :allk])
53+
gds = gatherby(ds, :ords)
54+
res="""# Keyword arguments
55+
"""
56+
for vds in eachgroup(gds)
57+
res *= "## $(vds[1,:cat])\n"
58+
for r in eachrow(vds)
59+
if !isempty(r[:docs])
60+
res *= " * `$(r[:allk])`: $(r[:docs]) "
61+
if r[:dfs] !== nothing
62+
res *= "default: `$(repr(r[:dfs]))`\n\n"
63+
else
64+
res *= "\n\n"
65+
end
66+
end
67+
end
68+
end
69+
res
70+
end
71+
72+
73+
74+
75+
Kwds_docs = Dict{Symbol, String}(
76+
:x2axis=>"When set to `true`, the top x-axis will be used for the current plot.",
77+
:y2axis=>"When set to `true`, the right y-axis will be used for the current plot.",
78+
:opacity=>"The mark opacity from 0 (transparent) to 1 (opaque).",
79+
:outlinethickness=>"The mark outline thickness.",
80+
:color=>"The default color for the mark. User can pass color's name as symbol (e.g. `:red`), as string (e.g. `\"red\"`), as HTML color value (e.g. `\"#4682b4\"`), or pass a gradient color using the `gradient()` function.",
81+
:colormodel => "It specifies the color scheme to use for the marks.",
82+
:outlinecolor=>"The mark's outline color.",
83+
:font=>"The font name for displaying text.",
84+
:fontsize=>"The font size for displaying text.",
85+
:italic=>"If `true` the italic style will be used for displaying text.",
86+
:fontweight=>"The font weight for displaying text, use 100 for thin font and 900 for the bold one.",
87+
:fontcolor=>"The text color.",
88+
:fontangle=>"The text angle.",
89+
:fontbaseline=>"The text baseline.",
90+
:fontalign=>"The text alignment.",
91+
:fontlimit=>"The maximum length of the text mark in pixels. The text value will be automatically truncated if the rendered size exceeds the limit.",
92+
:fontdir=>"The direction of the text, i.e. `:ltr` or `:rtl`.",
93+
:fontopacity=>"The text opacity.",
94+
:tooltip=>"A tooltip will be shown upon mouse hover.",
95+
:legend=>"User can pass a symbol to this keyword argument to indicate that more customisation will be passed for the legened of corresponding mark. User needs to provide the extra customisation via the `Legend` global keyword.",
96+
:clip=>"Indicates if the marks should be clipped.",
97+
98+
99+
100+
)
101+
102+
bar_normalizer(x) = x ./ sum(x)
103+
104+
BAR_DEFAULT = SGKwds(
105+
:x => __dic(:default=> 0, :__ord=>0, :__cat => "Required", :__doc=>"User should pass a single column for plotting the bar chart. User must pass either this or the `y` argument."),
106+
:y => __dic(:default=> 0, :__ord=>0, :__cat=> "Required", :__doc=>"User should pass a single column for plotting the bar chart. User must pass either this or the `x` argument."),
107+
:group => __dic(:default=> nothing, :__ord=>2, :__cat=>"Grouping", :__doc=>"A grouped bar chart will be created by passing a single column to this argument."),
108+
:response => __dic(:default=> nothing, :__ord=>1, :__cat=>"Bar options", :__doc=>"A numeric column which its aggregated values based on the `stat` keyword argument will be used to determine the height of each bar."),
109+
:stat => __dic(:default=> nothing, :__ord=>1, :__cat=>"Bar options", :__doc=>"A function for aggregating the `response` keyword argument. When `response` is passed the default value of the keword change to `IMD.sum`, however, user can pass any function to this argument. The function must accept two arguments `f`(format), and `x` the input values and return the aggregated values."),
110+
:normalize =>__dic(:default=> false, :__ord=>1, :__cat=>"Bar options", :__doc=>"If `true` the bars will be normalized in each group. By default the total bar heights will be `1` in each group, however, user can pass customised function via `normalizer` to change this behaviour."),
111+
:normalizer => __dic(:default=> bar_normalizer, :__ord=>1, :__cat=>"Bar options", :__doc=>"This function will be used to normalize bar height within each group."),
112+
:x2axis => __dic(:default=> false, :__ord=>5, :__cat=>"Axes options", :__doc=>Kwds_docs[:x2axis]),
113+
:y2axis => __dic(:default=> false, :__ord=>5, :__cat=>"Axes options", :__doc=>Kwds_docs[:y2axis]),
114+
:opacity => __dic(:default=> 1, :__ord=>3, :__cat=>"Bar appearance", :__doc=>Kwds_docs[:opacity]),
115+
:outlinethickness => __dic(:default=> 1, :__ord=>3, :__cat=>"Bar appearance", :__doc=>Kwds_docs[:outlinethickness]),
116+
:barwidth => __dic(:default=> 1, :__ord=>3, :__cat=>"Bar appearance", :__doc=>"The bar width proportion with respect to the available space. It can be any number between 0 and 1. User can pass `:nest` too, which in this case the bar width will be automatically calculated for each group in such a way that the bar widths in each group will be smaller than the previous group. Users can pass `nestfactor` to control how fast they would like the bar width change for each group."),
117+
:nestfactor => __dic(:default=> nothing, :__ord=>3, :__cat=>"Bar appearance", :__doc=>"When the `barwidth` keyword is set to `:nest` this will control how much change should be applied to the current group barwidth compared to the previous one. By default this will be controlled automatically."),
118+
:color => __dic(:default=> "#4682b4", :__ord=>3, :__cat=>"Bar appearance", :__doc=>Kwds_docs[:color]),
119+
:colorresponse =>__dic(:default=> nothing, :__ord=>1, :__cat=>"Bar options", :__doc=>"Name/index of a numeric column which will be used to change the fill color of each bar based on its values. The function passed to `colorstat` will be used to aggregate the values if there are more than one observation for a specific bar."),
120+
:colorstat => __dic(:default=> nothing, :__ord=>1, :__cat=>"Bar options", :__doc=>"The function that will be used to aggregate values in column passed as `colorresponse`."),
121+
:colormodel => __dic(:default=> :diverging, :__ord=>1, :__cat=>"Bar options", :__doc=>Kwds_docs[:colormodel]), # we use linear scale to produce colors
122+
:space => __dic(:default=> 0.1, :__ord=>3, :__cat=>"Bar appearance", :__doc=>"The space between bars. It can be any number between 0 and 1. "), # the space between bars - the space is calculated as space * total_bandwidth
123+
:groupspace => __dic(:default=> 0.05, :__ord=>2, :__cat=>"Grouping", :__doc=>"The space between bars inside each group when `groupdisplay` is in (`:cluster`, `:step`)."), # the space between bars inside each group - for groupdisplay = :cluster
124+
:outlinecolor => __dic(:default=> :white, :__ord=>3, :__cat=>"Bar appearance", :__doc=>Kwds_docs[:outlinecolor]),
125+
:groupdisplay => __dic(:default=> :stack, :__ord=>2, :__cat=>"Grouping", :__doc=>"Indicate how to display bars in each group. It can be `:stack`, `:cluster`, `:step`, or `:none`."), #:stack, :cluster, :step (i.e. stacked and cluster), or :none
126+
:grouporder => __dic(:default=> :ascending, :__ord=>2, :__cat=>"Grouping", :__doc=>"How to order values in each group. It can be `:data`, `:ascending`, or `:descending`. User may also pass a vector of group levels to dictate the orders."), # :data, :ascending, :descending, userdefined order (by giving a vector of group level) - having a group column in panelby can cause some issues
127+
:orderresponse => __dic(:default=> nothing, :__ord=>1, :__cat=>"Bar options", :__doc=>"Name of a numeric column which will be used to change the order of bars. The function passed to `orderstat` will be used to aggregate the values if there are more than one observation for a specific bar. Note that the axis' order will override this."), # by default axis order control it, but it can be controlled by a column
128+
:orderstat => __dic(:default=> nothing, :__ord=>1, :__cat=>"Bar options", :__doc=>"The function that will be used to aggregate values in column passed as `orderresponse`."), # freq is default aggregator, however, it can be any other function
129+
:baseline => __dic(:default=> 0, :__ord=>1, :__cat=>"Bar options", :__doc=>"The start value for drawing bars."),
130+
:baselineresponse => __dic(:default=> nothing, :__ord=>1, :__cat=>"Bar options", :__doc=>"Name/index of a numeric column which will be used to change the baseline of bars within each group. The function passed to `baselinestat` will be used to aggregate the values if there are more than one observation for a specific bar."), # each bar (or each group when groupped) can have its own baseline
131+
:baselinestat => __dic(:default=> nothing, :__ord=>1, :__cat=>"Bar options", :__doc=>"The function that will be used to aggregate values in column passed as `baselineresponse`."), # same rule as :stat
132+
133+
#data label
134+
:label=>__dic(:default=> :none, :__ord=>4, :__cat=>"Bar labels", :__doc=>"What information should be used for bar labels. It can be :none, :height, or :category."), # :height or :category
135+
:labelfont=>__dic(:default=> nothing, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:font]),
136+
:labelbaseline=>__dic(:default=> nothing, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:fontbaseline]),
137+
:labelfontweight=>__dic(:default=> nothing, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:fontweight]),
138+
:labelitalic=>__dic(:default=> nothing, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:italic]),
139+
:labelsize=>__dic(:default=> nothing, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:fontsize]),
140+
:labelcolor=>__dic(:default=> :black, :__ord=>4, :__cat=>"Bar labels", :__doc=>"The text color. User can also pass `:group` or `:colorresponse` to use the corresponding scale for coloring the text."),# allow :group, :colorresponse to use their color if available
141+
:labelangle=>__dic(:default=> nothing, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:fontangle]),
142+
:labeldir=>__dic(:default=> :ltr, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:fontdir]),
143+
:labellimit=>__dic(:default=> nothing, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:fontlimit]),
144+
:labeloffset=>__dic(:default=> 0, :__ord=>4, :__cat=>"Bar labels", :__doc=>"The amount in pixel to offset the labels."),
145+
:labelpos => __dic(:default=> :end, :__ord=>4, :__cat=>"Bar labels", :__doc=>"The position of labels within each bar. It can be `:end`, `:start`, or `:middle`."), # :end, :start, :middle
146+
:labelloc=>__dic(:default=> 0.5, :__ord=>4, :__cat=>"Bar labels", :__doc=>"relative location of the label within each bar. It can be any number between 0 and 1, where 0.5 means middle of the bar."), # between 0 and 1
147+
:labeld3format=>__dic(:default=> "", :__ord=>4, :__cat=>"Bar labels", :__doc=>"d3 format for labels."),
148+
:labelopacity=>__dic(:default=> 1, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:fontopacity]),
149+
:labelalign=>__dic(:default=> nothing, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:fontalign]),
150+
:labelalternate=>__dic(:default=> true, :__ord=>4, :__cat=>"Bar labels", :__doc=>"Use automatic alogirhtms to adjust the labels for bar with negative heights."), # if true, it automatically change the baseline, align and offset of the label text
151+
:tooltip => __dic(:default=> false, :__ord=>4, :__cat=>"Bar labels", :__doc=>Kwds_docs[:tooltip]), # it can be true, only if labelresponse is provided
152+
153+
154+
:legend => __dic(:default=> nothing, :__ord=>6, :__cat=>"Legend", :__doc=>Kwds_docs[:legend]),
155+
156+
:barcorner => __dic(:default=> 0, :__ord=>3, :__cat=>"Bar appearance", :__doc=>"Corner radius for bars. (`cornerRadiusTopLeft`, `cornerRadiusTopRight`, `cornerRadiusBottomLeft`, `cornerRadiusBottomRight`)."), #corner radius (cornerRadiusTopLeft, cornerRadiusTopRight, cornerRadiusBottomLeft, cornerRadiusBottomRight)
157+
158+
:missingmode => __dic(:default=> 0, :__ord=>1, :__cat=>"Bar options", :__doc=>"Indicate how to handle missing values in category or group. `0` = nothing, `1` = no missing in category, `2` = no missing in group, `3` = no missing in category or group, `4` = no missing in category and group."),
159+
160+
161+
:clip => __dic(:default=> nothing, :__ord=>7, :__cat=>"Miscellaneous", :__doc=>Kwds_docs[:clip]),
162+
)

0 commit comments

Comments
 (0)