Skip to content

Commit

Permalink
support :point in errorbar
Browse files Browse the repository at this point in the history
  • Loading branch information
pnezis committed Dec 22, 2023
1 parent 538b71f commit eb20a18
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 28 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- Add `Tucan.errorbar/3` plot

```tucan
Tucan.errorbar(:barley, "yield", group_by: "variety")
Tucan.errorbar(:barley, "yield", group_by: "variety", points: true, ticks: true)
|> Tucan.color_by("variety")
```

Expand Down
59 changes: 47 additions & 12 deletions lib/tucan.ex
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,15 @@ defmodule Tucan do
type: :boolean,
doc: "If set ticks will be included to the error bars ends",
dest: :mark
],
points: [
type: :boolean,
doc: "If set the means of the error bar are also included."
],
point_color: [
type: :string,
doc: "The color of the points if enabled. If not set defaults to the error bars color",
section: :style
]
]

Expand Down Expand Up @@ -887,10 +896,11 @@ defmodule Tucan do
Tucan.errorbar(:barley, "yield", group_by: "variety", orient: :vertical)
```
By setting `:ticks` to `true` you can enable errorbar end ticks:
By setting `:ticks` to `true` you can enable errorbar end ticks. Also you can overlay the
means by setting `:points` to `true`.
```tucan
Tucan.errorbar(:barley, "yield", group_by: "variety", ticks: true)
Tucan.errorbar(:barley, "yield", group_by: "variety", ticks: true, points: true)
```
You can change the `:extent` value in order to configure how the error bars are extended. Below
Expand All @@ -913,17 +923,16 @@ defmodule Tucan do
You can use `:line_color` and `:stroke_width` to modify the look of the error bars:
```tucan
Tucan.errorbar(:barley, "yield", group_by: "variety", line_color: "red", stroke_width: 3, ticks: true)
Tucan.errorbar(:barley, "yield", group_by: "variety", line_color: "red", stroke_width: 3, ticks: true, points: true)
```
You can color categories by combining it with `Tucan.color_by/3`.
```tucan
Tucan.errorbar(:barley, "yield", group_by: "variety")
Tucan.errorbar(:barley, "yield", group_by: "variety", points: true)
|> Tucan.color_by("variety")
```
"""
# TODO: add :points option to support points
# TODO: options for configuring tick color and width
@doc section: :plots
@spec errorbar(plotdata :: plotdata(), field :: String.t(), opts :: keyword()) :: VegaLite.t()
Expand All @@ -939,14 +948,40 @@ defmodule Tucan do
end)
|> Keyword.delete(:stroke_width)

errorbar_layer =
Vl.new()
|> Vl.mark(:errorbar, mark_opts)
|> encode_field(:x, field, opts, type: :quantitative, scale: [zero: false])
|> maybe_encode_field(:y, fn -> opts[:group_by] != nil end, opts[:group_by], opts,
type: :nominal
)
|> maybe_flip_axes(opts[:orient] == :vertical)

points_layer =
if opts[:points] do
points_spec_opts =
Tucan.Keyword.put_not_nil(
[filled: true],
:color,
opts[:point_color] || opts[:line_color]
)

[
Vl.new()
|> Vl.mark(:point, points_spec_opts)
|> Vl.encode_field(:x, field, type: :quantitative, aggregate: "mean")
|> maybe_encode_field(:y, fn -> opts[:group_by] != nil end, opts[:group_by], opts,
type: :nominal
)
|> maybe_flip_axes(opts[:orient] == :vertical)
]
else
[]
end

plotdata
|> new(spec_opts)
|> Vl.mark(:errorbar, mark_opts)
|> encode_field(:x, field, opts, type: :quantitative, scale: [zero: false])
|> maybe_encode_field(:y, fn -> opts[:group_by] != nil end, opts[:group_by], opts,
type: :nominal
)
|> maybe_flip_axes(opts[:orient] == :vertical)
|> new(spec_opts ++ [tucan: [multilayer: true]])
|> layers([errorbar_layer] ++ points_layer)
end

boxplot_opts = [
Expand Down
69 changes: 54 additions & 15 deletions test/tucan_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -700,43 +700,82 @@ defmodule TucanTest do
expected =
Vl.new()
|> Vl.data_from_url(@barley_dataset)
|> Vl.mark(:errorbar, extent: :stderr, fill_opacity: 1)
|> Vl.encode_field(:x, "yield", type: :quantitative, scale: [zero: false])
|> Vl.layers([
Vl.new()
|> Vl.mark(:errorbar, extent: :stderr, fill_opacity: 1)
|> Vl.encode_field(:x, "yield", type: :quantitative, scale: [zero: false])
])

assert Tucan.errorbar(@barley_dataset, "yield") == expected
assert_plot(Tucan.errorbar(@barley_dataset, "yield"), expected)
end

test "with ticks" do
expected =
Vl.new()
|> Vl.data_from_url(@barley_dataset)
|> Vl.mark(:errorbar, extent: :stderr, fill_opacity: 1, ticks: true)
|> Vl.encode_field(:x, "yield", type: :quantitative, scale: [zero: false])
|> Vl.layers([
Vl.new()
|> Vl.mark(:errorbar, extent: :stderr, fill_opacity: 1, ticks: true)
|> Vl.encode_field(:x, "yield", type: :quantitative, scale: [zero: false])
])

assert Tucan.errorbar(@barley_dataset, "yield", ticks: true) == expected
assert_plot(Tucan.errorbar(@barley_dataset, "yield", ticks: true), expected)
end

test "with ticks and points" do
expected =
Vl.new()
|> Vl.data_from_url(@barley_dataset)
|> Vl.layers([
Vl.new()
|> Vl.mark(:errorbar, extent: :stderr, fill_opacity: 1, ticks: true)
|> Vl.encode_field(:x, "yield", type: :quantitative, scale: [zero: false]),
Vl.new()
|> Vl.mark(:point, filled: true)
|> Vl.encode_field(:x, "yield", type: :quantitative, aggregate: :mean)
])

assert_plot(Tucan.errorbar(@barley_dataset, "yield", ticks: true, points: true), expected)
end

test "with grouping" do
expected =
Vl.new()
|> Vl.data_from_url(@barley_dataset)
|> Vl.mark(:errorbar, extent: :stderr, fill_opacity: 1)
|> Vl.encode_field(:x, "yield", type: :quantitative, scale: [zero: false])
|> Vl.encode_field(:y, "variety", type: :nominal)
|> Vl.layers([
Vl.new()
|> Vl.mark(:errorbar, extent: :stderr, fill_opacity: 1)
|> Vl.encode_field(:x, "yield", type: :quantitative, scale: [zero: false])
|> Vl.encode_field(:y, "variety", type: :nominal)
])

assert Tucan.errorbar(@barley_dataset, "yield", group_by: "variety") == expected
assert_plot(Tucan.errorbar(@barley_dataset, "yield", group_by: "variety"), expected)
end

test "with grouping and vertical orientation" do
expected =
Vl.new()
|> Vl.data_from_url(@barley_dataset)
|> Vl.mark(:errorbar, extent: :stderr, fill_opacity: 1)
|> Vl.encode_field(:y, "yield", type: :quantitative, scale: [zero: false])
|> Vl.encode_field(:x, "variety", type: :nominal)
|> Vl.layers([
Vl.new()
|> Vl.mark(:errorbar, extent: :stderr, fill_opacity: 1)
|> Vl.encode_field(:y, "yield", type: :quantitative, scale: [zero: false])
|> Vl.encode_field(:x, "variety", type: :nominal),
Vl.new()
|> Vl.mark(:point, filled: true, color: "red")
|> Vl.encode_field(:y, "yield", type: :quantitative, aggregate: :mean)
|> Vl.encode_field(:x, "variety", type: :nominal)
])

assert Tucan.errorbar(@barley_dataset, "yield", group_by: "variety", orient: :vertical) ==
expected
assert_plot(
Tucan.errorbar(@barley_dataset, "yield",
group_by: "variety",
orient: :vertical,
points: true,
point_color: "red"
),
expected
)
end
end

Expand Down

0 comments on commit eb20a18

Please sign in to comment.