Giter VIP home page Giter VIP logo

cssmakielayout.jl's Introduction

CSSMakieLayout.jl

This library helps in the development of reactive frontends and can be used alongside WGLMakie and JSServe.

The functions you care about

Most frequently you will be using the hstack (row of items), vstack (column of items), and zstack functions to create your HTML/CSS layout. You will be wrapping your figures in HTML div tags with wrap.

When stacking things with zstack you will want to select which one is currently viewable with the active function and the activeidx keyword argument. Transitions between the states can also be enabled with the anim keyword argument. One can select [:default], [:whoop], [:static] , [:opacity] or a valid combination of the four.

Hover animations are available with the hoverable function with the specified anim keyword. One can select [:default], [:border] or a combination of the two.

And for convenience you can create clickable buttons that navigate the layout with modifier.

The workflow can be defined as such:

  • Reactiveness centers around the observable objects.

  • There are three kinds of CSSMakieLayout elements: static, modifiers and modifiable

  • The static elements are purely for styling, with no reactive component. For example hstack, vstack, wrap and hoverable if no observable is set for the stayactiveif parameter

  • The modifiers are the ones that modify the observables that in turn modity the modifiable elements. For now there exists only one modifier element that is luckily called modifier. It takes the observable to be modified as the parameter keyword, and the way in which to modify it as the action keyword (which can be :toggle, :increase, :decrease, :increasecap, :decreasecap, :increasemod, :decreasemod)

  • The modifiable elements are the ones that get modified by an observable: zstack, hoverable with the stayactiveif observable set and selectclass

Focus on the styling and let us handle the reactive part!

Let's go through two examples on how to use this library, the first one will be a simple one, and the second, more complex.

Example 1 Example 2
"examples/assets/example1.gif" "examples/assets/example2.gif"

Example 1

For example let's say we want to create a view in which we can visualize one of three figures (a, b and c) in a slider manner. We also want to control the slider with two buttons: LEFT and RIGHT. The RIGHT button slided to the next figure and the LEFT one slides to the figure before.

The layout would look something like this:

"< (left) | 1 | (right) >"

By acting on the buttons, one moves from one figure to the other.

This can be easily implemented using CSSMakieLayout.jl

  1. First of all include the library in your project
using WGLMakie
WGLMakie.activate!()
using JSServe
using Markdown

# 1. LOAD LIBRARY   
using CSSMakieLayout
  1. Then define your layout using CSSMakieLayout.jl,
config = Dict(
    :resolution => (1400, 700), #used for the main figures
)

landing = App() do session::Session
    CSSMakieLayout.CurrentSession = session

    # Active index: 1 2 or 3
    #   1: the first a.k.a 'a' figure is active
    #   2: the second a.k.a 'b' figure is active    
    #   3: the third a.k.a 'c' figure is active
    # This observable is used to communicate between the zstack and the selection menu/buttons as such: the selection buttons modify the observable which in turn, modifies the active figure zstack.
    activeidx = Observable(1)

    # Create the buttons and the mainfigures
    mainfigures = [Figure(backgroundcolor=:white,  resolution=config[:resolution]) for _ in 1:3]
    
    buttons = [modifier(wrap(DOM.h1("")); action=:decreasecap, parameter=activeidx, cap=3),
                modifier(wrap(DOM.h1("")); action=:increasecap, parameter=activeidx, cap=3)]
    
    
    axii = [Axis(mainfigures[i][1, 1]) for i in 1:3]
    # Plot each of the 3 figures using your own plots!
    scatter!(axii[1], 0:0.1:10, x -> sin(x))
    scatter!(axii[2], 0:0.1:10, x -> tan(x))
    scatter!(axii[3], 0:0.1:10, x -> log(x))

    # Obtain the reactive layout using a zstack controlled by the activeidx observable
    activefig = zstack(
                active(mainfigures[1]),
                wrap(mainfigures[2]),
                wrap(mainfigures[3]);
                activeidx=activeidx,
                style="width: $(config[:resolution][1])px")
    

    layout = hstack(buttons[1], activefig, buttons[2])
    
    
    return hstack(CSSMakieLayout.formatstyle, layout)

end
  1. And finally Serve the app
isdefined(Main, :server) && close(server);
port = 8888
interface = "127.0.0.1"
server = JSServe.Server(interface, port);
JSServe.HTTPServer.start(server)
JSServe.route!(server, "/" => landing);


# the app will run on localhost at port 8888
wait(server)

This code can be visualized at ./examples/example_readme, or at https://github.com/adrianariton/QuantumFristGenRepeater (this will be updated shortly with the plots of the first gen repeater)

Example 2

This time we are going to create a selectable layout with a menu, that will look like this:

"< (left) | 1 | (right) >"

To do this we will follow the same stept, with a modified layout function:

  1. First of all include the library in your project
using WGLMakie
WGLMakie.activate!()
using JSServe
using Markdown

# 1. LOAD LIBRARY   
using CSSMakieLayout
  1. Create the layout
config = Dict(
    :resolution => (1400, 700), #used for the main figures
    :smallresolution => (280, 160), #used for the menufigures
)

# define some additional style for the menufigures' container
menufigs_style = """
    display:flex;
    flex-direction: row;
    justify-content: space-around;
    background-color: rgb(242, 242, 247);
    padding-top: 20px;
    width: $(config[:resolution][1])px;
"""

landing = App() do session::Session
    CSSMakieLayout.CurrentSession = session

    # Create the menufigures and the mainfigures
    mainfigures = [Figure(backgroundcolor=:white,  resolution=config[:resolution]) for _ in 1:3]
    menufigures = [Figure(backgroundcolor=:white,  resolution=config[:smallresolution]) for _ in 1:3]
    # Figure titles
    titles= ["Figure a: sin(x)",
            "Figure b: tan(x)",
            "Figure c: cos(x)"]
    
    # Active index/ hovered index: 1 2 or 3
    #   1: the first a.k.a 'a' figure is active / hovered respectively
    #   2: the second a.k.a 'b' figure is active / hovered respectively
    #   3: the third a.k.a 'c' figure is active / hovered respectively
    # These two observables are used to communicate between the zstack and the selection menu/buttons as such: the selection buttons modify the observables which in turn, modify the active figure zstack.
    activeidx = Observable(1)
    hoveredidx = Observable(0)

    # Add custom click event listeners
    for i in 1:3
        on(events(menufigures[i]).mousebutton) do event
            activeidx[]=i  
            notify(activeidx)
        end
        on(events(menufigures[i]).mouseposition) do event
            hoveredidx[]=i  
            notify(hoveredidx)
        end
    end

    # Axii of each of the 6 figures
    main_axii = [Axis(mainfigures[i][1, 1]) for i in 1:3]
    menu_axii = [Axis(menufigures[i][1, 1]) for i in 1:3]

    # Plot each of the 3 figures using your own plots!
    scatter!(main_axii[1], 0:0.1:10, x -> sin(x))
    scatter!(main_axii[2], 0:0.1:10, x -> tan(x))
    scatter!(main_axii[3], 0:0.1:10, x -> log(x))

    scatter!(menu_axii[1], 0:0.1:10, x -> sin(x))
    scatter!(menu_axii[2], 0:0.1:10, x -> tan(x))
    scatter!(menu_axii[3], 0:0.1:10, x -> log(x))

    
    # Create ZStacks displaying titles below the menu graphs
    titles_zstack = [zstack(wrap(DOM.h4(titles[i], class="upper")),
                            wrap(""); 
                            activeidx=@lift(($hoveredidx == i || $activeidx == i)),
                            anim=[:opacity], style="""color: $(config[:colorscheme][2]);""") for i in 1:3]



    # Wrap each of the menu figures and its corresponing title zstack in a div
    menufigs_andtitles = wrap([
            vstack(
                hoverable(menufigures[i], anim=[:border];
                        stayactiveif=@lift($activeidx == i)),
                titles_zstack[i];
                class="justify-center align-center "    
            ) for i in 1:3]; 
            class="menufigs",
            style=menufigs_style
        )
    
    # Create the active figure zstack and add the :whoop (zoom in) animation to it
    activefig = zstack(
                active(mainfigures[1]),
                wrap(mainfigures[2]),
                wrap(mainfigures[3]);
                activeidx=activeidx,
                anim=[:whoop])

    # Obtain reactive layout of the figures 
    return wrap(menufigs_andtitles, activefig, CSSMakieLayout.formatstyle)

end
  1. And finally Serve the app
isdefined(Main, :server) && close(server);
port = 8888
interface = "127.0.0.1"
server = JSServe.Server(interface, port);
JSServe.HTTPServer.start(server)
JSServe.route!(server, "/" => landing);


# the app will run on localhost at port 8888
wait(server)

cssmakielayout.jl's People

Contributors

adrianariton avatar oxinabox avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

cssmakielayout.jl's Issues

Normalized size across monitors (retina)

Hello!

I am an active user of WGLMakie and JSServe, I wrote a small package, ParamViz.jl, unregistered as well for now, that I use to deploy web apps on a package docs.

An issue I am running into, is that WGLMakie figures are set in pixels, and therefore vary with the resolution of the monitor. On some monitors, my figures appear very big, and others, very small. I wish I could have my figures be a proportion of the monitor or web page...
It is an issue Simon Danish (author of Makie and JSServe) is aware of, but fixing it is not a priority.

I imagine css would help, and it seems your package can do just that?
I am not sure though, but I hope your package helps, I will experiment and let you know how it goes!

First round of review

Great job, guys! This looks already quite great! Let's do some final polish.

I structure my feedback as (1) main points with a reason for why it helps to make such changes followed by (2) specific examples of where the change would help.

dependencies

[deps]
ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Example = "7876af07-990d-54b4-ab0e-23690620f79a"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
JSServe = "824d6782-a2ef-11e9-3a09-e5662e0c26f9"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
WGLMakie = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"

do we need ConcurrentSim, Distributions, Documenter, Example, Graphs, ResumableFunctions, Revise as dependencies? These seem like accidental noise that has poluted the Project.toml

Even Documenter is not needed - the docs project can have one

Using JSServe.jsrender

Awesome package :) Looking forward to see it being used & registered.
One thing that should be changed though is this:

    landing2 = App() do session::Session
        CssMakieLayout.CurrentSession = session
        ...
    end

This is pretty hacky, since Session isn't something that's easily globally shared.
Also, it's somewhat unnecessary for any user to set it, if you properly overload JSServe.jsrender(s::Session, my_object).
I haven't looked into this in detail, but I'd propose something like this:
All the functions like zstack should define a small custom struct, like this:

struct ZStack 
    items::Vector{Any}
    attributes::Dict{Symbol, Any}
end
# properly add attributes like: activeidx::Observable=nothing class="", anim=[:default], style="", md=false
zstack(items...; kw...)  = ZStack(Any[items...], Dict{Symbol, Any}(kw))

function JSServe.jsrender(session::Session, zstack::ZStack)
       .....
        # add on(activeidx) event
        onjs(session, activeidx, js"""function on_update(new_value) {
            const activefig_stack = $(item_div)
            for(i = 1; i <= $(height); ++i) {
                const element = activefig_stack.querySelector(":nth-child(" + i +")")
                element.classList.remove("CssMakieLayout_active");
                if(i == new_value) {
                    element.classList.add("CssMakieLayout_active");
                }
            }
        }
        """)
    end
    return item_div
end

Anything else will be pretty hacky, but this should pretty nicely integrate with JSServe, so one can return any CssMakieLayout struct in an App.
Also, I'd put formatstyle into a css file and insert it in all jsrender calls as an asset: DOM.div(Asset("path/to/formatstyle.css")).
JSServe will make sure, that it will get included only one time, so don't worry about putting it in every jsrender.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.