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
xxxxxxxxxxSelect classes
Class 1
Class 2
Class 3
xxxxxxxxxx1
1
dataset DrawData(classes=Set([1,2,3]))xxxxxxxxxx5
1
begin2
scatter(X1, Y1)3
scatter!(X2, Y2)4
scatter!(X3, Y3)5
end209.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
xxxxxxxxxx1
1
datasetxxxxxxxxxx11
1
begin2
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;xxxxxxxxxx1
1
using PlotsColor map →
xxxxxxxxxxJavascript source code
xxxxxxxxxxCss source code
xxxxxxxxxxxxxxxxxxxx119
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: Smoothing60
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 path69
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
""";xxxxxxxxxx35
1
begin2
Base. struct DrawData3
width::Int=3004
height::Int=1505
​6
classes::Set{Int}=Set(1) 7
end8
​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 t12
i, class = t13
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 |> join16
​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 classes25
<hr>26
</p>27
$classes_options28
</div>29
<script>30
$js_code31
</script>32
</div>33
""")34
end35
endxxxxxxxxxx38
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_colorxxxxxxxxxx14
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 purple5
"#472c7a", # purple6
"#3b518b", # blue7
"#2c718e", # blue8
"#21908d", # blue-green9
"#27ad81", # green10
"#5cc863", # green11
"#aadc32", # lime green12
"#fde725", # yellow13
]))[class_idx]14
end@only_in_pluto (macro with 1 method)xxxxxxxxxx3
1
macro only_in_pluto(ex)2
isdefined(Main, :PlutoRunner) ? esc(ex) : quote end3
endxxxxxxxxxx