Open hhaensel opened 3 years ago
@hhaensel Thanks for the updates. The slider looks great and it doesn't seem to have large dependencies so it makes total sense to add to Stipple. Can you please share an example that I can test myself? Just brainstorming, I'm thinking in the line of adding some type hints (metadata) with the payload so that we somehow know how the data should be converted and maybe having a conversion API?
after some tweaking it looks quite similar to the quasar library ...
It looks lovely 😍
Here's an example without any encapsulation, mainly plain HTML ...
using Stipple
using StippleCharts
using StippleUI
using Genie, Genie.Router, Genie.Renderer.Html
import Genie.Renderer.Json.JSONParser.JSONText
using Colors
OptDict = Dict{Symbol, Any}
opts(;kwargs...) = OptDict(kwargs...)
const plot_opts_dict = OptDict(
:chart => OptDict(:type => :line),
:xaxis => OptDict(:type => :numeric),
:yaxis => OptDict(:min => -5, :max => 5, :tickAmount => 10,
:labels => OptDict(:formatter => JSONText("function(val, index) { return val.toFixed(1); }"))
)
)
rgb(c::Colorant) = Int.(255 .* (red(c), green(c), blue(c)))
Colors.color_names["var(--q-color-primary)"] = rgb(colorant"#0779e4")
Colors.color_names["var(--q-color-secondary)"] = rgb(colorant"#6cb1f2")
defaultcolors = [RGBA(0,143/255,251/255,0.85), RGBA(0,227/255,150/255,0.85), RGBA(254/255,176/255,25/255,0.85)]
setopacity(c::AbstractString, alpha) = setopacity(parse(Colorant, c), alpha)
function setopacity(c::Colorant, alpha)
cc = RGBA(c)
cc = RGBA(cc.r, cc.g, cc.b, convert(typeof(cc.r), cc.alpha .* alpha))
"#" * lowercase(Colors.hex(cc, :AUTO))
end
function dotOptions(color::Colorant;
bordercolor = color, borderradius = "50%", fontsize = "11px", transform = "",
boxshadow = "", transition="100ms")
bc = typeof(bordercolor) <: Color ? lowercase("#" * Colors.hex(bordercolor, :AUTO)) : setopacity(bordercolor, 1)
dotOptions(lowercase("#" * Colors.hex(color, :AUTO));
bordercolor = bc, borderradius = borderradius, fontsize = fontsize, transform = transform,
boxshadow = boxshadow, transition = transition)
end
function dotOptions(color::AbstractString="var(--q-color-primary)";
bordercolor = color, borderradius = "50%", fontsize = "11px", transform = "",
boxshadow = "", transition="100ms")
hovercolor = try
setopacity(color, 0.5)
catch ex
color
end
@info color, hovercolor
"""
{
style: {
'background-color': '$color',
'color': '$hovercolor',
'border-radius': '$borderradius',
'transition': '$transition'
},
focusStyle: {
'transform': '$transform',
'box-shadow': '$boxshadow',
'transition': '$transition'
},
tooltipStyle: {
'background-color': '$color',
'border-color': '$color',
'font-size' : '11px',
'font-family': 'Lato,sans-serif',
'font-weight': '700',
}
}
"""
end
xx = Base.range(0, 4π, length=200) |> collect
Base.@kwdef mutable struct HHDashboard <: ReactiveModel
name::R{String} = "World"
a::R{Array{Any}} = [1.0, 2, 2.5]
b::R{Array{Any}} = [0.0, π/6, π/3]
c::R{Float64} = 0.0
plot_data::R{Vector{PlotSeries}} = [PlotSeries(t, PlotData(zip(xx, a .* sin.(xx .- b) .+ c) |> collect)) for (t, a, b) in collect(zip(["Sine 1", "Sine 2", "Sine 3"], a, b))]
plot_options_dict::OptDict = plot_opts_dict
js_code::R{String} = ""
end
Stipple.register_components(HHDashboard, StippleCharts.COMPONENTS)
Stipple.register_components(HHDashboard, [:vueSlider => Symbol("window[ 'vue-slider-component' ]")])
# model = Stipple.init(HHDashboard())
models = Dict{String, ReactiveModel}()
row_module(args...) = row(cell(class="st-module", args...))
labeled_slider(label::AbstractString, size, args...; kwargs...) = row([cell(size=size, h6(label)), cell(slider(args...; kwargs...))])
function ui(user)
channel = string(hash(user))
model = if haskey(models, channel)
models[channel]
else
model = models[channel] = Stipple.init(HHDashboard(), channel = channel)
onany(model.a, model.b, model.c) do a, b, c
@info "amplitude: $a, phase: $b, offset: $c"
model.plot_data[] = [PlotSeries(t, PlotData(zip(xx, a .* sin.(xx .- b) .+ c) |> collect)) for (t, a, b) in collect(zip(["Sine 1", "Sine 2", "Sine 3"], a, b))]
end
return model
end
# update plot_data when a, b or c are changed
db = dashboard(vm(model), class="container", [
heading("Stipple x-y Line Plot"),
row_module([
h2(["Hello ", span("", @text(:name)), "!"]),
p("I am $user")
]),
row_module(
row([
cell(class="st-br st-ph", [
h5("What is your name?"),
textfield("", :name, placeholder="type your name", label="Name", outlined="", filled="")
]),
cell(class="st-br st-ph", [
h5("Sine oder Cosine?"),
row([
cell(size=3, h6("Amplitude")),cell(
"""
<vue-slider
class="q-slider__pin-text q-slider__pin-value-marker-text"
style="padding-top: 25px;"
v-model="a"
height="3px"
width="100%"
min="0"
max="3"
interval="0.1"
:order="false"
:dot-size="15"
:tooltip="'always'"
:dot-options = "[
$(join(dotOptions.(defaultcolors;
borderradius="25%", transform="rotate(45deg)"), ","))
]"
:process = "dotsPos => [[(dotsPos.length > 1) ? Math.min(...dotsPos) : 0, Math.max(...dotsPos), { 'background-color': 'var(--q-color-primary)' }]]"
></vue-slider>
""")
]),
row([
cell(size=3, h6("Phase")),cell(
"""
<vue-slider
class="q-slider__pin-text q-slider__pin-value-marker-text"
style="padding-top: 25px;"
v-model="b"
height="3px"
width="100%"
min="0"
max="3"
interval="0.1"
:order="false"
:dot-size="15"
:tooltip="'always'"
:dot-options = "[
$(join(dotOptions.(defaultcolors;
borderradius="25%", transform="rotate(45deg)"), ",")) ]"
:process = "dotsPos => [[(dotsPos.length > 1) ? Math.min(...dotsPos) : 0, Math.max(...dotsPos), { 'background-color': 'var(--q-color-primary)' }]]"
></vue-slider>
""")
]),
labeled_slider("Offset", 3, -3:0.01:3, :c; markers=true, label=true, labelalways=true)
])
])
),
row_module(plot(:plot_data; options=:plot_options_dict)),
# make a nice bottom section
row(" ")
], title = "Stipple x-y ApexChart", channel = channel)
css = join([
script(src="https://cdn.jsdelivr.net/npm/vue@latest/dist/vue.min.js"),
script(src="https://cdn.jsdelivr.net/npm/vue-slider-component@latest/dist/vue-slider-component.umd.min.js"),
link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/vue-slider-component@latest/theme/default.css"),
style("""
.vue-slider-dot-handle:hover {
box-shadow: 0px 0px 0px 6px;
transform: rotate(45deg);
transition: 100ms
}
.st-ph {
padding-left: 20px;
padding-right: 20px;
}
.st-ph:first-child {
padding-left: 0px;
}
.st-ph:last-child {
padding-right: 0px;
}
.st-pv {
padding-top: 20px;
padding-bottom: 20px;
}
.st-pv:first-child {
padding-top: 0px;
}
.st-pv:last-child {
padding-bottom: 0px;
}
.st-bb:last-child {
border-bottom: 0
}
""")
], "\n")
css * db |> html
end
route("/") do
# identify user from the header
headers = Genie.Requests.getheaders()
user = headers["User-Agent"] |> split |> last
# deliver a user-spcific ui
ui(user)
end
Genie.config.server_host = "127.0.0.1"
up(open_browser = true)
@hhaensel Sorry, missed this update! Can we release this somehow? Should we pack it as a distinct package or add it to StippleUI? If we put it in UI we need a way to load (additional) assets per component.
@essenciary These days I had the need for a slider with multiple handles. After some search I landed with vue-slider-component by @nightcatsama
It's a really nice component, but it is not part of the quasar library. Do you think it makes sense to include it nevertheless?
In this context I experienced two problems:
Any
This latter poses a general problem for Stipple. Typed arrays are not correctly handled by Stipple and the only way is to use arrays of type any.
I propose a solution along the following lines:
We could also hide this part in the Base.parse ... What do you think?
P.S.: If there are more complex data types, such as plots it gets even more difficult. If you change a ploton the front end, Stipple spits errors, because the chart data are not correctly resolved to julian data types. Solving this would be even more complicated. Perhaps you have an idea? Of, course read-only types would help here...