Skip to content

Commit c5935ef

Browse files
Merge pull request #52 from BigThinkcode/bubble_chart
Bubble chart
2 parents af00675 + 7ca610b commit c5935ef

File tree

6 files changed

+283
-1
lines changed

6 files changed

+283
-1
lines changed

lib/matplotex/figure/areal.ex

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ defmodule Matplotex.Figure.Areal do
141141
def materialized_by_region(figure) do
142142
figure
143143
|> Lead.set_regions_areal()
144+
|> Lead.transform_sizes()
144145
|> Cast.cast_xticks_by_region()
145146
|> Cast.cast_yticks_by_region()
146147
|> Cast.cast_hgrids_by_region()
@@ -184,6 +185,7 @@ defmodule Matplotex.Figure.Areal do
184185

185186
{x, y}
186187
end
188+
187189
def flatten_for_data(_, _bottom) do
188190
raise InputError, bottom: "Wrong data provided for opts bottom"
189191
end
@@ -433,10 +435,36 @@ defmodule Matplotex.Figure.Areal do
433435
|> transformation(y, xlim, ylim, width, height, transition)
434436
|> Algebra.flip_y_coordinate()
435437
end)
438+
|> maybe_wrap_with_sizes(dataset)
439+
|> maybe_wrap_with_colors()
436440

437441
%Dataset{dataset | transformed: transformed}
438442
end
439443

444+
defp maybe_wrap_with_sizes(transformed, %Dataset{sizes: sizes} = dataset)
445+
when length(transformed) == length(sizes) do
446+
{Enum.zip(transformed, sizes), dataset}
447+
end
448+
449+
defp maybe_wrap_with_sizes(
450+
transformed,
451+
%Dataset{colors: colors, marker_size: marker_size} = dataset
452+
)
453+
when length(transformed) == length(colors) do
454+
{Enum.zip(transformed, List.duplicate(marker_size, length(transformed))), dataset}
455+
end
456+
457+
defp maybe_wrap_with_sizes(transformed, dataset) do
458+
{transformed, dataset}
459+
end
460+
461+
defp maybe_wrap_with_colors({transformed, %Dataset{colors: colors}})
462+
when length(transformed) == length(colors) do
463+
Enum.zip(transformed, colors)
464+
end
465+
466+
defp maybe_wrap_with_colors({transformed, _dataset}), do: transformed
467+
440468
defp transform_with_bottom(x, y, xlim, ylim, width, height, transition) when is_list(y) do
441469
y_top = Enum.sum(y)
442470
y_bottom = y |> tl() |> Enum.sum()

lib/matplotex/figure/areal/scatter.ex

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule Matplotex.Figure.Areal.Scatter do
22
@moduledoc false
3+
alias Matplotex.Colorscheme.Garner
34
alias Matplotex.Figure.Areal.PlotOptions
45
alias Matplotex.Figure.Areal.Region
56
alias Matplotex.Figure.Areal.Ticker
@@ -31,7 +32,7 @@ defmodule Matplotex.Figure.Areal.Scatter do
3132
def create(%Figure{axes: %__MODULE__{dataset: data} = axes} = figure, {x, y}, opts \\ []) do
3233
x = determine_numeric_value(x)
3334
y = determine_numeric_value(y)
34-
dataset = Dataset.cast(%Dataset{x: x, y: y}, opts)
35+
dataset = Dataset.cast(%Dataset{x: x, y: y}, opts) |> Dataset.update_cmap()
3536
datasets = data ++ [dataset]
3637
xydata = flatten_for_data(datasets)
3738

@@ -97,6 +98,45 @@ defmodule Matplotex.Figure.Areal.Scatter do
9798
end
9899
end
99100

101+
def capture(
102+
[{{{x, y}, s}, color} | to_capture],
103+
captured,
104+
%Dataset{
105+
marker: marker,
106+
colors: colors,
107+
cmap: cmap
108+
} = dataset
109+
) do
110+
color = colors |> Enum.min_max() |> Garner.garn_color(color, cmap)
111+
112+
capture(
113+
to_capture,
114+
captured ++
115+
[
116+
Marker.generate_marker(marker, x, y, color, s)
117+
],
118+
dataset
119+
)
120+
end
121+
122+
def capture(
123+
[{{x, y}, s} | to_capture],
124+
captured,
125+
%Dataset{
126+
color: color,
127+
marker: marker
128+
} = dataset
129+
) do
130+
capture(
131+
to_capture,
132+
captured ++
133+
[
134+
Marker.generate_marker(marker, x, y, color, s)
135+
],
136+
dataset
137+
)
138+
end
139+
100140
def capture(
101141
[{x, y} | to_capture],
102142
captured,

lib/matplotex/figure/dataset.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
defmodule Matplotex.Figure.Dataset do
22
@moduledoc false
3+
alias Matplotex.Colorscheme.Colormap
4+
35
@default_color "blue"
46
@default_marker "o"
57
@default_linestyle "_"
@@ -31,4 +33,8 @@ defmodule Matplotex.Figure.Dataset do
3133
def cast(dataset, values) do
3234
struct(dataset, values)
3335
end
36+
37+
def update_cmap(%__MODULE__{cmap: cmap} = dataset) do
38+
%__MODULE__{dataset | cmap: Colormap.fetch_cmap(cmap)}
39+
end
3440
end

lib/matplotex/figure/lead.ex

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
defmodule Matplotex.Figure.Lead do
22
@moduledoc false
3+
alias Matplotex.InputError
4+
alias Matplotex.Figure.Dataset
35
alias Matplotex.Utils.Algebra
46
alias Matplotex.Figure.Areal.XyRegion.Coords, as: XyCoords
57
alias Matplotex.Figure.Font
@@ -45,6 +47,48 @@ defmodule Matplotex.Figure.Lead do
4547
%Figure{figure | axes: %{axes | border: {lx, by, rx, ty}}}
4648
end
4749

50+
def transform_sizes(%Figure{axes: %{dataset: datasets} = axes} = figure) do
51+
datasets = transform_dataset_sizes(figure, datasets, [])
52+
%Figure{figure | axes: %{axes | dataset: datasets}}
53+
end
54+
55+
defp transform_dataset_sizes(_figure, [%Dataset{sizes: nil} | _to_transorm] = datasets, _),
56+
do: datasets
57+
58+
defp transform_dataset_sizes(
59+
%Figure{
60+
axes: %{
61+
region_content: %Region{width: width_region_content, height: height_region_content}
62+
}
63+
} = figure,
64+
[%Dataset{sizes: sizes} = dataset | to_transorm],
65+
transformed
66+
)
67+
when length(sizes) > 0 do
68+
content_area = width_region_content * height_region_content
69+
total_size = Enum.sum(sizes)
70+
71+
area_size_ratio =
72+
if total_size > 0 do
73+
content_area / total_size * 2
74+
else
75+
raise InputError, message: "Invalid sizes for fractionizing area"
76+
end
77+
78+
sizes =
79+
sizes
80+
|> Nx.tensor()
81+
|> Nx.multiply(area_size_ratio)
82+
|> Nx.to_list()
83+
84+
transform_dataset_sizes(figure, to_transorm, [%Dataset{dataset | sizes: sizes} | transformed])
85+
end
86+
87+
defp transform_dataset_sizes(_figure, [%Dataset{sizes: []} | _to_transorm] = datasets, _),
88+
do: datasets
89+
90+
defp transform_dataset_sizes(_, [], transformed), do: transformed
91+
4892
defp set_frame_size(%Figure{margin: margin, figsize: {fwidth, fheight}, axes: axes} = figure) do
4993
frame_size = {fwidth - fwidth * 2 * margin, fheight - fheight * 2 * margin}
5094
lx = fwidth * margin

test/matplotex/figure/areal/scatter_test.exs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
defmodule Matplotex.Figure.Areal.ScatterTest do
2+
alias Matplotex.Figure.Areal
3+
alias Matplotex.Figure.Dataset
24
alias Matplotex.Figure.Areal.Scatter
35
alias Matplotex.Figure
46
use Matplotex.PlotCase
@@ -12,6 +14,51 @@ defmodule Matplotex.Figure.Areal.ScatterTest do
1214
assert %Figure{axes: %{data: {x, _y}, element: elements}} = Scatter.materialize(figure)
1315
assert Enum.count(elements, fn elem -> elem.type == "plot.marker" end) == length(x)
1416
end
17+
18+
test "generates elements with various saizes if it passed a size attrbute" do
19+
x = [1, 2, 3, 4, 5]
20+
y = [10, 20, 30, 40, 50]
21+
sizes = [1, 2, 3, 4, 5]
22+
23+
assert %Figure{axes: %{element: elements}} =
24+
x |> Matplotex.scatter(y, sizes: sizes) |> Figure.materialize()
25+
26+
[h | tail] =
27+
elements
28+
|> Enum.filter(fn x -> x.type == "plot.marker" end)
29+
|> Enum.map(fn x ->
30+
x.r
31+
end)
32+
33+
refute Enum.all?(tail, fn x -> x == h end)
34+
end
35+
36+
test "generates elements with various saizes and colors if it passed a size and color attrbute" do
37+
x = [1, 2, 3, 4, 5]
38+
y = [10, 20, 30, 40, 50]
39+
sizes = [1, 2, 3, 4, 5]
40+
41+
assert %Figure{axes: %{element: elements}} =
42+
x |> Matplotex.scatter(y, sizes: sizes, colors: sizes) |> Figure.materialize()
43+
44+
[h | tail] =
45+
elements
46+
|> Enum.filter(fn x -> x.type == "plot.marker" end)
47+
|> Enum.map(fn x ->
48+
x.r
49+
end)
50+
51+
refute Enum.all?(tail, fn x -> x == h end)
52+
53+
[h | tail] =
54+
elements
55+
|> Enum.filter(fn x -> x.type == "plot.marker" end)
56+
|> Enum.map(fn x ->
57+
x.fill
58+
end)
59+
60+
refute Enum.all?(tail, fn x -> x == h end)
61+
end
1562
end
1663

1764
describe "generate_ticks/2" do
@@ -24,4 +71,99 @@ defmodule Matplotex.Figure.Areal.ScatterTest do
2471
assert length(ticks) == 6
2572
end
2673
end
74+
75+
describe "do_transform" do
76+
test "zips transformed values with sizes if the dataset contains sizes in eaqual size" do
77+
x = [1, 2, 3, 4, 5]
78+
y = [10, 20, 30, 40, 50]
79+
sizes = [1, 2, 3, 4, 5]
80+
width = 2
81+
height = 2
82+
83+
assert %Figure{axes: %{dataset: [dataset]}} =
84+
x |> Matplotex.scatter(y, figsize: {width, height}, sizes: sizes)
85+
86+
assert %Dataset{transformed: transformed} =
87+
Areal.do_transform(
88+
dataset,
89+
Enum.min_max(x),
90+
Enum.min_max(y),
91+
width,
92+
height,
93+
{0, 0}
94+
)
95+
96+
assert Enum.all?(transformed, &match?({{_, _}, _}, &1))
97+
end
98+
99+
test "zips transformed values with marker size if colors exist without sizes" do
100+
x = [1, 2, 3, 4, 5]
101+
y = [10, 20, 30, 40, 50]
102+
colors = [1, 2, 3, 4, 5]
103+
width = 2
104+
height = 2
105+
106+
assert %Figure{axes: %{dataset: [%Dataset{marker_size: _marker_size} = dataset]}} =
107+
x |> Matplotex.scatter(y, figsize: {width, height}, colors: colors)
108+
109+
assert %Dataset{transformed: transformed} =
110+
Areal.do_transform(
111+
dataset,
112+
Enum.min_max(x),
113+
Enum.min_max(y),
114+
width,
115+
height,
116+
{0, 0}
117+
)
118+
119+
assert Enum.all?(transformed, &match?({{{_, _}, _marker_size}, _}, &1))
120+
end
121+
122+
test "zips transformed values with colors if the dataset contanis colors" do
123+
x = [1, 2, 3, 4, 5]
124+
y = [10, 20, 30, 40, 50]
125+
colors = [1, 2, 3, 4, 5]
126+
width = 2
127+
height = 2
128+
129+
assert %Figure{axes: %{dataset: [dataset]}} =
130+
x |> Matplotex.scatter(y, figsize: {width, height}, colors: colors)
131+
132+
assert %Dataset{transformed: transformed} =
133+
Areal.do_transform(
134+
dataset,
135+
Enum.min_max(x),
136+
Enum.min_max(y),
137+
width,
138+
height,
139+
{0, 0}
140+
)
141+
142+
assert Enum.all?(transformed, &match?({{{_, _}, _}, _}, &1))
143+
end
144+
145+
test "zips both size and colors if the dataset contains size and color" do
146+
x = [1, 2, 3, 4, 5]
147+
y = [10, 20, 30, 40, 50]
148+
sizes = [1, 2, 3, 4, 5]
149+
colors = [1, 2, 3, 4, 5]
150+
width = 2
151+
height = 2
152+
153+
assert %Figure{axes: %{dataset: [dataset]}} =
154+
x |> Matplotex.scatter(y, figsize: {width, height}, sizes: sizes, colors: colors)
155+
156+
assert %Dataset{transformed: transformed} =
157+
Areal.do_transform(
158+
dataset,
159+
Enum.min_max(x),
160+
Enum.min_max(y),
161+
width,
162+
height,
163+
{0, 0}
164+
)
165+
166+
assert Enum.all?(transformed, &match?({{{_, _}, _}, _}, &1))
167+
end
168+
end
27169
end

test/matplotex/figure/lead_test.exs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule Matplotex.Figure.LeadTest do
2+
alias Matplotex.Figure.Dataset
23
alias Matplotex.Figure.TwoD
34
alias Matplotex.Figure.Areal.Region
45
alias Matplotex.Figure
@@ -259,4 +260,25 @@ defmodule Matplotex.Figure.LeadTest do
259260
assert cx != 0 && cy != 0
260261
end
261262
end
263+
264+
describe "transform_sizes/1" do
265+
test "converts sizes to equivalent radius to the buble" do
266+
x = [1, 2, 3, 4, 5]
267+
y = [10, 20, 30, 40, 50]
268+
sizes = [1, 2, 3, 4, 5]
269+
width = 2
270+
height = 2
271+
272+
figure =
273+
x
274+
|> Matplotex.scatter(y, figsize: {width, height}, sizes: sizes)
275+
|> Lead.set_regions_areal()
276+
277+
assert %Figure{axes: %{dataset: [%Dataset{sizes: transformed_sizes}]}} =
278+
Lead.transform_sizes(figure)
279+
280+
assert length(transformed_sizes) == length(sizes)
281+
assert Enum.sum(transformed_sizes) < width * height * 1.5
282+
end
283+
end
262284
end

0 commit comments

Comments
 (0)