DrawData.jl
DrawData
is a @bind
widget to draw datasets and use them directly in Pluto.jl!
References
DrawData.jl is inspired by the followings:
The original DrawData python package for the idea
This blog post for the canvas doodling code
xxxxxxxxxx
Select classes
Class 1
Class 2
Class 3
xxxxxxxxxx
1
1
dataset DrawData(classes=Set([1,2,3]))
xxxxxxxxxx
5
1
begin
2
scatter(X1, Y1)
3
scatter!(X2, Y2)
4
scatter!(X3, Y3)
5
end
209.082
89.6177
1
213.235
87.5236
1
253.313
110.578
1
233.393
129.582
1
191.502
103.709
1
187
77.0823
1
189.866
70.2375
1
209.045
58.9728
1
214.538
58.7
1
222.779
62.8684
1
234
87.1844
1
221.699
106.85
1
219.197
107.7
1
195
84.886
1
213.99
55.706
1
245.512
68.4067
1
247.337
83.6897
1
43.5109
84.8913
2
53.3327
80.5337
2
63.376
76.908
2
180.947
14.9105
3
155.081
36.4838
3
152.812
36.7
3
150.544
37.4282
3
146
35.8296
3
143
31.6658
3
144.967
13.4746
3
149.349
11.2504
3
155.517
10.7
3
159
12.6124
3
xxxxxxxxxx
1
1
dataset
xxxxxxxxxx
11
1
begin
2
dataset1 = filter(x -> x[3] == 1, dataset)
3
dataset2 = filter(x -> x[3] == 2, dataset)
4
dataset3 = filter(x -> x[3] == 3, dataset)
5
xyz(dataset) = map(x -> x[1], dataset), map(x -> x[2], dataset), map(x -> x[3], dataset)
6
​
7
8
X1, Y1, Z1 = xyz(dataset1)
9
X2, Y2, Z2 = xyz(dataset2)
10
X3, Y3, Z3 = xyz(dataset3)
11
end;
xxxxxxxxxx
1
1
using Plots
Color map →
xxxxxxxxxx
Javascript source code
xxxxxxxxxx
Css source code
xxxxxxxxxx
xxxxxxxxxx
119
1
js_code = """
2
const div = currentScript.parentElement;
3
const canvas = div.querySelector("canvas");
4
const ctx = canvas.getContext("2d");
5
​
6
const GEN_THRESHOLD = .4;
7
​
8
const classSelectors = div.querySelectorAll(".draw_data_class_selector");
9
const canvasHeight = canvas.height;
10
​
11
function dispatchEvent() {
12
div.value = points;
13
div.dispatchEvent(new CustomEvent("input"))
14
}
15
​
16
const colors = [];
17
​
18
for (let i = 0; i < classSelectors.length; i++) {
19
const sel = classSelectors[i];
20
colors.push((sel.children[0].style.backgroundColor));
21
​
22
sel.addEventListener("click", () => {
23
classSelectors[currentClass - 1].classList.remove("selected");
24
sel.classList.add("selected");
25
currentClass = i+1;
26
});
27
}
28
​
29
let paint = false;
30
​
31
let clickX = [];
32
let clickY = [];
33
let clickDrag = [];
34
let currentClass = 1;
35
​
36
let points = [];
37
​
38
function addClick(x, y, dragging=false) {
39
clickX.push(x);
40
clickY.push(y);
41
clickDrag.push(dragging);
42
}
43
​
44
function redraw(){
45
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
46
​
47
ctx.strokeStyle = "#cccccc44";
48
ctx.lineJoin = "round";
49
ctx.lineWidth = 5;
50
​
51
for(let i = 0; i < clickX.length; i++) {
52
ctx.beginPath();
53
if (clickDrag[i] && i) {
54
ctx.moveTo(clickX[i-1], clickY[i-1]);
55
} else {
56
ctx.moveTo(clickX[i]-1, clickY[i]);
57
}
58
59
// TODO: Smoothing
60
ctx.lineTo(clickX[i], clickY[i]);
61
ctx.closePath();
62
ctx.stroke();
63
}
64
​
65
for (let i = 0; i < points.length; i++) {
66
const [x, y, cls] = points[i];
67
ctx.fillStyle = colors[cls-1];
68
ctx.beginPath(); //Start path
69
ctx.arc(x, canvasHeight - y, 4, 0, Math.PI * 2, true);
70
ctx.fill();
71
}
72
}
73
​
74
canvas.addEventListener("mousedown", function(e) {
75
var mouseX = e.pageX - canvas.offsetLeft;
76
var mouseY = e.pageY - canvas.offsetTop;
77
​
78
paint = true;
79
const rect = canvas.getBoundingClientRect();
80
​
81
addClick(e.clientX - rect.left, e.clientY - rect.top);
82
redraw();
83
});
84
​
85
canvas.addEventListener("mousemove", function(e) {
86
if (paint) {
87
const rect = canvas.getBoundingClientRect();
88
89
const newClickX = e.clientX - rect.left;
90
const newClickY = e.clientY - rect.top;
91
​
92
const previousClickX = clickX[clickX.length - 1];
93
const previousClickY = clickY[clickY.length - 1];
94
​
95
const shouldGenerate = Math.random() < GEN_THRESHOLD;
96
​
97
if (shouldGenerate) {
98
const ratio = Math.random();
99
100
const newPointX = previousClickX + (newClickX - previousClickX) * ratio;
101
const newPointY = previousClickY + (newClickY - previousClickY) * ratio;
102
​
103
points.push([newPointX, canvasHeight - newPointY, currentClass]);
104
dispatchEvent();
105
}
106
​
107
addClick(newClickX, newClickY, true);
108
redraw();
109
}
110
});
111
​
112
canvas.addEventListener("mouseup", function(e) {
113
paint = false;
114
});
115
​
116
canvas.addEventListener("mouseleave", function(e) {
117
paint = false;
118
});
119
""";
xxxxxxxxxx
35
1
begin
2
Base. struct DrawData
3
width::Int=300
4
height::Int=150
5
​
6
classes::Set{Int}=Set(1)
7
end
8
​
9
Base.get(::DrawData) = []
10
function Base.show(io::IO, m::MIME"text/html", dd::DrawData)
11
classes_options = map(enumerate(sort(collect(dd.classes)))) do t
12
i, class = t
13
color = get_color(i)
14
"""<p class="$(i == 1 ? "selected" : "") draw_data_class_selector"><span style="background-color: $color"></span> Class $class</p>"""
15
end |> join
16
​
17
​
18
write(io, """
19
<div class="draw_data">
20
<style>$css_code</style>
21
<canvas width="$(dd.width)px" height="$(dd.height)px"></canvas>
22
<div class="draw_data_utils">
23
<p>
24
Select classes
25
<hr>
26
</p>
27
$classes_options
28
</div>
29
<script>
30
$js_code
31
</script>
32
</div>
33
""")
34
end
35
end
xxxxxxxxxx
38
1
css_code = """
2
.draw_data {
3
display: flex;
4
}
5
​
6
.draw_data canvas {
7
border-radius: 4px;
8
border: 1px solid #ccc;
9
}
10
​
11
.draw_data .draw_data_utils {
12
padding: 5px;
13
flex: 1;
14
}
15
​
16
.draw_data .draw_datas_utils p {
17
width: 100%;
18
}
19
​
20
.draw_data_class_selector {
21
cursor: pointer;
22
padding: 5px;
23
margin-bottom: 4px;
24
border-radius: 4px;
25
}
26
​
27
.draw_data .selected.draw_data_class_selector {
28
background-color: #eee;
29
}
30
​
31
.draw_data .draw_data_class_selector span {
32
border-radius: 50%;
33
width: 0.9em;
34
height: 0.9em;
35
display: inline-block;
36
background-color: #fcc;
37
}
38
""";
Main.workspace2.get_color
xxxxxxxxxx
14
1
"get_color maps an index to a color code using the viridis color palette"
2
function get_color(class_idx)
3
Dict{Int,String}(enumerate([
4
"#440154", # dark purple
5
"#472c7a", # purple
6
"#3b518b", # blue
7
"#2c718e", # blue
8
"#21908d", # blue-green
9
"#27ad81", # green
10
"#5cc863", # green
11
"#aadc32", # lime green
12
"#fde725", # yellow
13
]))[class_idx]
14
end
@only_in_pluto (macro with 1 method)
xxxxxxxxxx
3
1
macro only_in_pluto(ex)
2
isdefined(Main, :PlutoRunner) ? esc(ex) : quote end
3
end
xxxxxxxxxx