msaJustPyUI¶
FastAPI adapted JustPy version for Integration of justpy UI Web Framework to msaAppService, which allows simple adding of routes to justpy webpages functions..
Just another Web UI option beside pure HTML or the Amis/Admin capabilities. Most important, it is also a Vue.js basis.
Integrate as Mixin in a Starlette or FastAPI inherited class.
Introduction¶
JustPy is an object-oriented, component based, high-level Python Web Framework that requires no front-end programming. With a few lines of only Python code, you can create interactive websites without any JavaScript programming. JustPy can also be used to create graphic user interfaces for Python programs.
Unlike other web frameworks, JustPy has no front-end/back-end distinction. All programming is done on the back-end allowing a simpler, more productive, and more Pythonic web development experience. JustPy removes the front-end/back-end distinction by intercepting the relevant events on the front-end and sending them to the back-end to be processed.
Note
JustPy 0.9.3 is fully integrated in msaSDK and runs under the MSAAPI/FastAPI umbrella, you just call a function by a route that async gives back a jp.WebPage. Websocket etc. runs all under MSAAPI/FastAPI.
In JustPy, elements on the web page are instances of component classes. A component in JustPy is a Python class that allows you to instantiate reusable custom elements whose functionality and design is encapsulated away from the rest of your code.
Custom components can be created using other components as building blocks. Out of the box, JustPy comes with support for html_components and svg_components components as well as more complex components such as charts and grids. It also supports most of the components and the functionality of the Quasar library of Material Design 2.0 components.
JustPy encourages creating your own components and reusing them in different projects (and, if applicable, sharing these components with others).
JustPy also supports visualization using matplotlib and Highcharts.
JustPy integrates very nicely with pandas and simplifies building web sites based on pandas analysis. JustPy comes with a pandas extension that makes it simple to create interactive charts and grids from pandas data structures.
Hopefully, this JustPy Integration will enable simple web development with Python courses by reducing the complexity of web development.
Original JustPy Project - GitHub Repo
Different Usage
Just use import msaJustPyUI as jp
intead of import justpy as jp
Simple Example for usage with MSAApp¶
Required MSAApp Service Definition (Settings):
ui_justpy: bool = True
# Optional to get the demos router mounted
ui_justpy_demos: bool = True
Create the app object first
from msaJustPyUI.jpcore.justpy_app import JustpyApp
from fastapi import FastAPI
class MSAFastAPI(FastAPI, JustpyApp):
"""
a MSAFastAPI application is a special FastAPI application
It includes justpy UI routing
"""
...
app = MSAFastAPI()
Integrated Demo's¶
Black Jack - Cards Demo¶
import msaJustPyUI as jp
import asyncio
card_size = 0.5
card_width = int(226 * card_size)
card_height = int(card_width * 314 / 226)
button_classes = """
inline-flex items-center px-6 py-6 border border-gray-700 text-2xl leading-6 font-bold rounded-md text-gray-700
bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800
active:bg-gray-50 transition ease-in-out duration-150
"""
div_classes = "items-center px-6 py-6 border border-gray-700 text-3xl leading-6 font-bold rounded-md text-gray-700; bg-white "
class Card(jp.Img):
"""
Card component: an extended image with predefined width and height and a transition
"""
def __init__(self, **kwargs):
"""
constructor
"""
super().__init__(**kwargs)
self.width = card_width
self.height = card_height
self.set_class("ml-2")
self.transition = {
"load": "transition origin-left ease-out duration-1000",
"load_start": "transform scale-x-0",
"load_end": "transform scale-x-100",
"enter": "transition origin-left ease-out duration-1000",
"enter_start": "transform scale-x-0",
"enter_end": "transform scale-x-100",
}
async def create_deck():
"""
get a new deck
"""
deck = await jp.get("https://deckofcardsapi.com/api/deck/new/shuffle/?deck_count=1")
return deck["deck_id"]
async def deal(deck_id, count=1):
"""
deal
"""
try:
cards = await jp.get(
f"https://deckofcardsapi.com/api/deck/{deck_id}/draw/?count={count}"
)
except:
print("error in deal")
return cards
def hand_value(hand):
"""
calculate the hand value
"""
value = 0
aces = 0
for card in hand:
if card.value == "ACE":
aces += 1
value += 1
else:
try:
value += int(card.value)
except:
value += 10
if aces and value < 12:
value += 10
return value
async def hit(self, msg):
"""
react on hit button
"""
wp = msg.page
card_dict = jp.Dict(await deal(wp.deck_id))
card = card_dict.cards[0]
wp.player_hand.append(card)
wp.player_div.add(Card(src=card.image))
player_hand_value = hand_value(wp.player_hand)
if player_hand_value < 22:
wp.hand_value_div.text = f"Hand value: {player_hand_value}"
else:
wp.hand_value_div.text = f"YOU HAVE BUSTED, Hand value: {player_hand_value}"
dealer_hand_value = hand_value(wp.dealer_hand)
result_div = jp.Div(classes=div_classes, a=wp.outer_div)
result_div.text = f"YOU LOST, Your hand: {player_hand_value}, Dealer's hand: {dealer_hand_value}"
for btn in [wp.stand_btn, wp.hit_btn]:
btn.disabled = True
btn.set_classes("cursor-not-allowed bg-gray-200 opacity-50")
wp.play_again_btn.remove_class("hidden")
async def stand(self, msg):
"""
react on stand button
"""
wp = msg.page
# Show dealer card
wp.card_back.set_class("hidden")
wp.down_card.remove_class("hidden")
for btn in [wp.stand_btn, wp.hit_btn]:
btn.disabled = True
btn.set_classes("cursor-not-allowed bg-gray-200 opacity-50")
await wp.update()
await asyncio.sleep(1.1)
while True:
dealer_hand_value = hand_value(wp.dealer_hand)
if dealer_hand_value > 16:
break
card_dict = jp.Dict(await deal(wp.deck_id))
card = card_dict.cards[0]
wp.dealer_hand.append(card)
wp.dealer_div.add(Card(src=card.image))
await wp.update()
await asyncio.sleep(1.1)
player_hand_value = hand_value(wp.player_hand)
result_div = jp.Div(classes=div_classes, a=wp.outer_div)
if (dealer_hand_value > 21) or (dealer_hand_value < player_hand_value):
result_div.text = f"YOU WON, Your hand: {player_hand_value}, Dealer's hand: {dealer_hand_value}"
elif dealer_hand_value > player_hand_value:
result_div.text = f"YOU LOST, Your hand: {player_hand_value}, Dealer's hand: {dealer_hand_value}"
else:
result_div.text = f"IT IS A DRAW, Your hand: {player_hand_value}, Dealer's hand: {dealer_hand_value}"
wp.play_again_btn.remove_class("hidden")
async def play_again(self, msg):
"""
react on play again button
"""
wp = msg.page
await wp.reload()
async def blackjack():
"""
the async web page to serve
"""
wp = jp.WebPage()
wp.outer_div = jp.Div(
classes="container mx-auto px-4 sm:px-6 lg:px-8 space-y-5", a=wp
)
wp.deck_id = await create_deck()
# Deal initial four cards
cards = jp.Dict(await deal(wp.deck_id, 4))
wp.player_hand = [cards.cards[0], cards.cards[2]]
wp.dealer_hand = [cards.cards[1], cards.cards[3]]
jp.Div(
text="Blackjack Demo",
a=wp.outer_div,
classes="m-2 p-4 text-3xl font-bold leading-7 text-gray-900 sm:leading-9 sm:truncate",
)
wp.dealer_div = jp.Div(classes="flex flex-wrap m-2", a=wp.outer_div)
# Image of back of card
wp.card_back = Card(
src="https://raw.githubusercontent.com/elimintz/elimintz.github.io/master/card_back.png",
a=wp.dealer_div,
)
wp.down_card = Card(src=wp.dealer_hand[0].image, a=wp.dealer_div, classes="hidden")
Card(src=wp.dealer_hand[1].image, a=wp.dealer_div, style="transition-delay: 1000ms")
wp.player_div = jp.Div(classes="flex flex-wrap m-2", a=wp.outer_div)
for card in wp.player_hand:
Card(src=card.image, a=wp.player_div, style="transition-delay: 2000ms")
button_div = jp.Div(classes="flex m-2 space-x-6", a=wp.outer_div)
wp.stand_btn = jp.Button(
text="Stand", a=button_div, classes=button_classes, click=stand
)
wp.hit_btn = jp.Button(text="Hit", a=button_div, classes=button_classes, click=hit)
wp.play_again_btn = jp.Button(
text="Play Again", a=button_div, classes=button_classes, click=play_again
)
wp.play_again_btn.set_class("hidden")
wp.hand_value_div = jp.Div(
text=f"Hand value: {hand_value(wp.player_hand)}",
a=wp.outer_div,
classes="text-2xl",
)
return wp
async def cards_demo():
return await blackjack()
Click Event Demo¶
class ClickDemo:
"""
demo for click handling
"""
async def onDivClick(self, msg):
"""
handle a click on the Div
"""
print(msg)
self.clickCount += 1
msg.target.text = f"I was clicked {self.clickCount} times"
async def click_demo(self):
"""
the example Webpage under test
"""
import msaJustPyUI as jp
wp = jp.WebPage(debug=False)
self.clickCount = 0
d = jp.Div(
text="Not clicked yet",
a=wp,
classes="w-48 text-xl m-2 p-1 bg-blue-500 text-white",
)
d.on("click", self.onDivClick)
d.additional_properties = [
"screenX",
"pageY",
"altKey",
"which",
"movementX",
"button",
"buttons",
]
return wp
async def click_demo():
clickDemo = ClickDemo()
return await clickDemo.click_demo()
Dogs App Demo¶
# see https://github.com/justpy-org/justpy/blob/master/examples/dogs.py
from justpy import *
from starlette.requests import Request
# https://dog.ceo/api/breeds/list/all dict of all breeds under
breeds = [
"affenpinscher",
"african",
"airedale",
"akita",
"appenzeller",
"basenji",
"beagle",
"bluetick",
"borzoi",
"bouvier",
"boxer",
"brabancon",
"briard",
"bullterrier-staffordshire",
"cairn",
"cattledog-australian",
"chihuahua",
"chow",
"clumber",
"cockapoo",
"collie-border",
"coonhound",
"corgi-cardigan",
"cotondetulear",
"dachshund",
"dalmatian",
"deerhound-scottish",
"dhole",
"dingo",
"doberman",
"elkhound-norwegian",
"entlebucher",
"eskimo",
"frise-bichon",
"germanshepherd",
"greyhound-italian",
"groenendael",
"hound-blood",
"hound-english",
"hound-ibizan",
"hound-walker",
"husky",
"keeshond",
"kelpie",
"komondor",
"kuvasz",
"labrador",
"leonberg",
"lhasa",
"malamute",
"malinois",
"maltese",
"mastiff-bull",
"mastiff-tibetan",
"mexicanhairless",
"mix",
"mountain-bernese",
"mountain-swiss",
"newfoundland",
"otterhound",
"papillon",
"pekinese",
"pembroke",
"pinscher-miniature",
"pointer-german",
"pomeranian",
"pug",
"puggle",
"pyrenees",
"redbone",
"retriever-chesapeake",
"retriever-curly",
"retriever-flatcoated",
"retriever-golden",
"ridgeback-rhodesian",
"rottweiler",
"saluki",
"samoyed",
"schipperke",
"schnauzer-giant",
"schnauzer-miniature",
"setter-english",
"setter-gordon",
"setter-irish",
"sheepdog-english",
"sheepdog-shetland",
"shiba",
"shihtzu",
"spaniel-blenheim",
"spaniel-brittany",
"spaniel-cocker",
"spaniel-irish",
"spaniel-japanese",
"spaniel-sussex",
"spaniel-welsh",
"springer-english",
"stbernard",
"terrier-american",
"terrier-australian",
"terrier-bedlington",
"terrier-border",
"terrier-dandie",
"terrier-fox",
"terrier-irish",
"terrier-kerryblue",
"terrier-lakeland",
"terrier-norfolk",
"terrier-norwich",
"terrier-patterdale",
"terrier-russell",
"terrier-scottish",
"terrier-sealyham",
"terrier-silky",
"terrier-tibetan",
"terrier-toy",
"terrier-westhighland",
"terrier-wheaten",
"terrier-yorkshire",
"vizsla",
"weimaraner",
"whippet",
"wolfhound-irish",
]
async def dog_test(_request):
"""
create a reactive webpage for dog pictures taken from
"""
wp = QuasarPage()
wp.body_style = "overflow: hidden"
bp = parse_html(
"""
<div class="q-pa-md">
<q-layout view="HHH lpr Fff" container style="height: 95vh" class="shadow-2 rounded-borders">
<q-header reveal class="bg-purple">
<q-toolbar name="toolbar">
<q-btn flat round dense icon="menu"/>
<q-toolbar-title>Dog Pictures</q-toolbar-title>
<q-btn glossy label="Next Picture" classes="q-mr-sm" name="next_picture"></q-btn>
</q-toolbar>
</q-header>
<q-drawer name="drawer" width=300 breakpoint=700 show-if-above elevated content-class="bg-white text-blue">
<q-scroll-area class="fit">
<div class="q-pa-sm">
<q-list name="thumbnail_list">
</q-list>
</div>
</q-scroll-area>
</q-drawer>
<q-footer class="bg-purple text-white">
<div class="text-white q-pa-xs" style="height: 30px;" name="footer"></div>
</q-footer>
<q-page-container style="overflow: hidden;">
<q-page padding name="main_page" style="overflow: hidden;">
<div style="height: 400px; width: 600px">
<q-img src="https://images.dog.ceo/breeds/papillon/n02086910_6087.jpg" class="cursor-pointer" name="image" >
</div>
<q-tooltip content-class="bg-purple" content-style="font-size: 13px" transition-show="scale" transition-hide="scale" anchor="top middle" name="tooltip">
Click image for next picture
</q-tooltip>
</q-img>
</q-page>
</q-page-container>
</q-layout>
</div>
""",
a=wp,
)
main_image = bp.name_dict["image"]
l = Link(
href="https://quasar.dev", text="Quasar", target="_blank", classes="text-white"
)
bp.name_dict["footer"].add(l)
bp.name_dict["tooltip"].disable_events = True
main_image.breed = "papillon"
breed_select = QBtnDropdown(
auto_close=True,
split=False,
glossy=True,
label="Select Breed",
icon="fas fa-dog",
a=bp.name_dict["toolbar"],
)
breed_list = QList(separator=True, dense=True, a=breed_select)
async def change_breed(self, msg):
main_image.breed = self.breed
await change_pic(main_image, msg)
breed_select.label = self.breed
for breed in breeds:
breed_item_html = f"""<q-item clickable v-close-menu v-ripple>
<q-item-section >
<q-item-label>{breed}</q-item-label>
</q-item-section>
</q-item>"""
# breed_item = parse_html(breed_item_html).first()
breed_item = parse_html(breed_item_html)
breed_item.breed = breed
breed_item.on("click", change_breed)
breed_list.add(breed_item)
def add_thumbnail(self, _msg):
"""
add a thumbnail
"""
list_item_html = f"""<q-item clickable v-ripple>
<q-item-section thumbnail>
<img src="{self.src}"/>
</q-item-section>
<q-item-section>{self.breed}</q-item-section>
<q-item-section> <q-btn class="gt-xs text-grey-8" size="12px" flat dense round icon="delete" name="delete"/></q-item-section>
</q-item>"""
list_item = parse_html(list_item_html)
list_item.breed = self.breed
list_item.src = self.src
list_item.name_dict["delete"].list = bp.name_dict["thumbnail_list"]
list_item.name_dict["delete"].list_item = list_item
bp.name_dict["thumbnail_list"].add(list_item)
def display_thumbnail(self, _msg):
"""
display a thumbnail
"""
main_image.src = self.src
list_item.on("mouseenter", display_thumbnail)
def delete_list_item(self, _msg):
"""
delete a list item
"""
self.list.remove(self.list_item)
list_item.name_dict["delete"].on("click", delete_list_item)
async def change_pic(self, msg):
"""
change the pictures
"""
# https://dog.ceo/api/breed/bulldog/french/images/random
if "-" in self.breed:
b = self.breed.split("-")
r = await get(f"https://dog.ceo/api/breed/{b[0]}/{b[1]}/images/random")
else:
r = await get(f"https://dog.ceo/api/breed/{self.breed}/images/random")
self.src = r["message"]
add_thumbnail(self, msg)
async def next_pic(self, msg):
"""
react on next picture clicked
"""
return await change_pic(main_image, msg)
# allow clicking an image
main_image.on("click", change_pic)
bp.name_dict["next_picture"].on("click", next_pic)
# initial picture
await change_pic(main_image, {})
return wp
async def dogs_demo(request: Request):
return await dog_test(request)
Happiness Dataset Demo - World Happiness Ranking¶
from msaJustPyUI.ui_demos.happiness import happiness_demo, corr_stag_test, corr_test
app.add_jproute("/ui/happiness", happiness_demo)
# the sub routes in the happiness demo
app.add_jproute("/corr_staggered", corr_stag_test)
app.add_jproute("/corr", corr_test)
import msaJustPyUI as jp
import pandas as pd
import numpy as np
import asyncio
from starlette.requests import Request
# https://worldhappiness.report/ed/2019/
df = pd.read_csv("http://elimintz.github.io/happiness_report_2019.csv").round(3)
def grid_change(self, msg):
msg.page.df = jp.read_csv_from_string(msg.data)
c = msg.page.df.jp.plot(
"Country",
msg.page.cols_to_plot,
kind="column",
title="World Happiness Ranking",
subtitle="Click and drag in the plot area to zoom in. Shift + drag to pan",
stacking=msg.page.stacking,
temp=True,
)
msg.page.c.options = c.options
def stack_change(self, msg):
msg.page.c.options.plotOptions.series.stacking = self.value
msg.page.stacking = self.value
def series_change(self, msg):
msg.page.cols_to_plot = []
for i, toggle in enumerate(msg.page.toggle_list):
if toggle.value:
msg.page.cols_to_plot.append(i + 3)
c = msg.page.df.jp.plot(
"Country",
msg.page.cols_to_plot,
kind="column",
title="World Happiness Ranking",
subtitle="Click and drag in the plot area to zoom in. Shift + drag to pan",
stacking=msg.page.stacking,
temp=True,
)
msg.page.c.options = c.options
async def corr_button_click(self, msg):
msg.page.open = "/corr"
await msg.page.update()
msg.page.open = ""
return True
def happiness_plot(request):
# ['Country', 'Rank', 'Score', 'Unexplained', 'GDP', 'Social_support', 'Health', 'Freedom', 'Generosity', Corruption']
wp = jp.QuasarPage()
chart_theme = request.query_params.get("theme", 8) # Default is 'grid'
themes = [
"high-contrast-dark",
"high-contrast-light",
"avocado",
"dark-blue",
"dark-green",
"dark-unica",
"gray",
"grid-light",
"grid",
"sand-signika",
"skies",
"sunset",
]
wp.highcharts_theme = themes[int(chart_theme)]
wp.stacking = ""
wp.cols_to_plot = [3, 4, 5, 6, 7, 8, 9]
wp.df = df
d = jp.Div(classes="q-ma-lg", a=wp)
bg = jp.QBtnToggle(
push=True,
glossy=True,
toggle_color="primary",
value="",
a=d,
input=stack_change,
style="margin-right: 30px",
)
bg.options = [
{"label": "No Stacking", "value": ""},
{"label": "Normal", "value": "normal"},
{"label": "percent", "value": "percent"},
]
wp.toggle_list = []
corr_button = jp.QBtn(
label="Show pairwise correlation", a=d, click=corr_button_click
)
for i in df.columns[3:]:
wp.toggle_list.append(
jp.QToggle(
checked_icon="check",
color="green",
unchecked_icon="clear",
value=True,
label=f"{i}",
a=d,
input=series_change,
)
) # ,style='margin-right: 10px',
chart = df.jp.plot(
"Country",
wp.cols_to_plot,
kind="column",
a=wp,
title="World Happiness Ranking",
subtitle="Click and drag in the plot area to zoom in. Shift + drag to pan",
stacking="",
classes="border m-2 p-2 q-ma-lg p-ma-lg",
)
grid = df.jp.ag_grid(a=wp)
grid.options.columnDefs[0].rowDrag = True
for event_name in ["sortChanged", "filterChanged", "columnMoved", "rowDragEnd"]:
grid.on(event_name, grid_change)
wp.c = chart
grid.c = chart
bg.c = chart
return wp
class ScatterWithRegression(jp.Scatter):
def __init__(self, x, y, **kwargs):
super().__init__(x, y, **kwargs)
x = np.asarray(x)
y = np.asarray(y)
m = (len(x) * np.sum(x * y) - np.sum(x) * np.sum(y)) / (
len(x) * np.sum(x * x) - np.sum(x) ** 2
)
b = (np.sum(y) - m * np.sum(x)) / len(x)
s = jp.Dict() # The new series
s.type = "line"
s.marker.enabled = False
s.enableMouseTracking = False
min = float(x.min())
max = float(x.max())
s.data = [[min, m * min + b], [max, m * max + b]]
s.name = f"Regression, m: {round(m, 3)}, b: {round(b, 3)}"
self.options.series.append(s)
chart_list = []
def create_corr_page():
# creates a page showing correlation between factors
score_factors = [
"Unexplained",
"GDP",
"Social_support",
"Health",
"Freedom",
"Generosity",
"Corruption",
]
wp = jp.WebPage(delete_flag=False)
d = jp.Div(a=wp, classes="flex flex-wrap")
for x_col in score_factors:
for y_col in score_factors[score_factors.index(x_col) + 1 :]:
x = df[x_col]
y = df[y_col]
chart = ScatterWithRegression(
x,
y,
a=d,
style="width: 400px; height: 400px; margin: 10px;",
classes="border",
)
chart_list.append(chart)
o = chart.options
o.title.text = f"{x_col} vs. {y_col}"
o.xAxis.title.text = x_col
o.yAxis.title.text = y_col
o.tooltip.pointFormat = f"{{point.country}}<br/></b>{x_col}: <b>{{point.x}}</b><br/></b>{y_col}: <b>{{point.y}}</b><br/>"
o.tooltip.useHtml = True
o.series[0].name = f"{x_col} vs. {y_col}"
data = []
# Make the first series data a dictionary so that the country name will be available to the tooltip
for i, point in enumerate(o.series[0].data):
data.append({"x": point[0], "y": point[1], "country": df["Country"][i]})
o.series[0].data = data
corr_def = (
df[
[
"Unexplained",
"GDP",
"Social_support",
"Health",
"Freedom",
"Generosity",
"Corruption",
]
]
.corr()
.round(3)
)
corr_def.insert(loc=0, column="Factors/Factors", value=corr_def.index)
g = corr_def.jp.ag_grid(a=wp, style="width: 90%; height: 250px", classes="m-2")
return wp
corr_page = create_corr_page()
async def corr_test():
return corr_page
async def page_ready(self, msg):
wp = msg.page
for chart in chart_list:
wp.d.add(chart)
await self.update()
await asyncio.sleep(0.5)
# Loads charts one by one to page, improving user experience
async def corr_stag_test():
wp = jp.WebPage()
wp.on("page_ready", page_ready)
wp.d = jp.Div(a=wp, classes="flex flex-wrap")
return wp
async def happiness_demo(request: Request):
return happiness_plot(request)
Iris Dataset Demo¶
import msaJustPyUI as jp
import pandas as pd
iris = pd.read_csv(
"https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv"
)
iris_stats = iris.describe().round(3)
iris_stats.insert(loc=0, column="stats", value=iris_stats.index)
iris_species = list(iris["species"].unique())
# Create a dictionary of frames per iris species
iris_species_frames = {}
for s in iris_species:
iris_species_frames[s] = iris.loc[iris["species"] == s]
async def click_point(self, msg):
# print(msg)
return await self.select_point(
[
{"id": chart_id, "series": msg.series_index, "point": msg.point_index}
for chart_id in self.chart_list
if self.id != chart_id
],
msg.websocket,
)
async def tooltip_formatter(self, msg):
# print(msg)
tooltip_html = f"""
<div style="color: {msg.color}"><span>●</span> {msg.series_name}</div>
<div style="color: {msg.color}">{self.col1}: {msg.x}</div>
<div style="color: {msg.color}">{self.col2}: {msg.y}</div>
"""
await self.draw_crosshair(
[
{"id": chart_id, "series": msg.series_index, "point": msg.point_index}
for chart_id in self.chart_list
],
msg.websocket,
)
return await self.tooltip_update(tooltip_html, msg.websocket)
def iris_data():
wp = jp.WebPage(highcharts_theme="gray", title="Iris Dataset", debug=True)
jp.Div(
text="Iris Dataset",
classes="text-3xl m-2 p-2 font-medium tracking-wider text-yellow-300 bg-gray-800 text-center",
a=wp,
)
d1 = jp.Div(classes="m-2 p-2 border-2", a=wp)
chart_list = []
for i, col1 in enumerate(iris.columns[:4]):
d2 = jp.Div(classes="flex", a=d1)
for j, col2 in enumerate(iris.columns[:4]):
if i != j: # Not on the diagonal
chart = jp.HighCharts(
a=d2, style="width: 300px; height: 300px", classes="flex-grow m-1"
)
chart_list.append(chart.id)
chart.chart_list = chart_list
chart.on("tooltip", tooltip_formatter)
chart.tooltip_y = 85
chart.on("point_click", click_point)
chart.col1 = col1
chart.col2 = col2
o = chart.options
o.chart.type = "scatter"
o.chart.zoomType = "xy"
o.title.text = ""
o.legend.enabled = False
o.credits.enabled = (
False if i < 3 or j < 3 else True
) # https://api.highcharts.com/highcharts/credits.enabled
o.xAxis.title.text = col2 if i == 3 else ""
o.yAxis.title.text = col1 if j == 0 else ""
o.xAxis.crosshair = o.yAxis.crosshair = True
for k, v in iris_species_frames.items():
s = jp.Dict()
s.name = k
s.allowPointSelect = True # https://api.highcharts.com/highcharts/series.scatter.allowPointSelect
s.marker.states.select.radius = 8
s.data = list(zip(v.iloc[:, j], v.iloc[:, i]))
o.series.append(s)
else:
chart = jp.Histogram(
list(iris.iloc[:, j]),
a=d2,
style="width: 300px; height: 300px",
classes="flex-grow m-1",
)
o = chart.options
o.title.text = ""
o.legend.enabled = False
o.xAxis[0].title.text = col2 if i == 3 else ""
o.xAxis[1].title.text = ""
o.yAxis[0].title.text = col1 if j == 0 else ""
o.yAxis[1].title.text = ""
o.credits.enabled = False if i < 3 or j < 3 else True
# Add two grids, first with the data and second with statistics describing the data
iris.jp.ag_grid(
a=wp,
classes="m-2 p-2",
style="height: 500px; width: 800px",
auto_size=True,
theme="ag-theme-balham-dark",
)
iris_stats.jp.ag_grid(
a=wp,
classes="m-2 p-2 border",
style="height: 500px; width: 950px",
auto_size=True,
theme="ag-theme-material",
)
return wp
async def iris_demo():
return iris_data()
Quasar Demo¶
import random
async def my_click(self, msg):
self.color = random.choice(
[
"primary",
"secondary",
"accent",
"dark",
"positive",
"negative",
"info",
"warning",
]
)
self.label = self.color
msg.page.dark = not msg.page.dark
await msg.page.set_dark_mode(msg.page.dark)
def quasar_example():
wp = jp.QuasarPage(dark=True) # Load page in dark mode
d = jp.Div(classes="q-pa-md q-gutter-sm", a=wp)
jp.QBtn(color="primary", icon="mail", label="On Left", a=d, click=my_click)
jp.QBtn(color="secondary", icon_right="mail", label="On Right", a=d, click=my_click)
jp.QBtn(
color="red",
icon="mail",
icon_right="send",
label="On Left and Right",
a=d,
click=my_click,
)
jp.Br(a=d)
jp.QBtn(
icon="phone",
label="Stacked",
stack=True,
glossy=True,
color="purple",
a=d,
click=my_click,
)
return wp
async def quasar_demo():
return quasar_example()
Notification Demo¶
from msaJustPyUI.ui_demos.after import after_click_demo
app.add_jproute("/ui/after", after_click_demo)
import msaJustPyUI as jp
btn_classes = jp.Styles.button_outline + " m-2"
notification_classes = "m-2 text-center text-xl bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded w-64"
def btn_click(self, msg):
self.nl[self.list_index].show = True
def btn_after(self, msg):
self.nl[self.list_index].show = False
def after_demo():
wp = jp.WebPage()
btn_list = []
notification_list = []
for index, btn_text in enumerate(["First", "Second", "Third"]):
btn_list.append(
jp.Button(
text=f"{btn_text} Button",
classes=btn_classes,
a=wp,
nl=notification_list,
list_index=index,
click=btn_click,
after=btn_after,
)
)
for notification_text in ["First", "Second", "Third"]:
notification_list.append(
jp.Div(
text=f"{notification_text} Notification",
classes=notification_classes,
a=wp,
show=False,
)
)
return wp
# initialize the demo
async def after_click_demo():
Drag & Drop Demo¶
import msaJustPyUI as jp
def drag_start(self, msg):
print("in drag start")
print(msg)
msg.page.image.animation = False
return True
def drop(self, msg):
print("in drop")
print(msg)
wp = msg.page
wp.image.animation = "zoomIn"
if self.index != wp.current_index:
wp.div_list[self.index].add(wp.image)
wp.div_list[wp.current_index].components = []
wp.current_index = self.index
def drag_test():
wp = jp.WebPage()
wp.current_index = 0
drag_options = {"drag_classes": "text-white bg-red-500"}
wp.image = jp.Img(
src="https://www.python.org/static/community_logos/python-powered-h-140x182.png",
height=100,
width=100,
classes="border faster",
drag_options=drag_options,
animation="zoomIn",
)
wp.image.on("dragstart", drag_start)
d = jp.Div(classes="flex flex-wrap", a=wp)
wp.div_list = [
jp.Div(
style="height: 160px; width: 130px",
classes="m-4 border-2 flex items-center justify-center",
drop=drop,
a=d,
index=i,
)
for i in range(30)
]
for div in wp.div_list:
div.events.append("dragover")
wp.div_list[0].add(wp.image)
return wp
async def drag_demo():
return drag_test()
Multiple Uploads Demo¶
from justpy import Form, Input, Li, Ol, WebPage
# see https://github.com/elimintz/justpy/pull/401
def handle_submit(_c, msg):
"""
handle submission of a multi upload
"""
fl = msg.page.file_list
fl.components.clear()
for fd in msg.form_data:
if "files" in fd:
for f in fd["files"]:
Li(text=f"File uploaded: {f['name']} of {len(f['file_content'])}", a=fl)
def multiupload():
"""
show a multi upload
"""
wp = WebPage()
WebPage.tailwind = False
f = Form(submit=handle_submit, a=wp)
Input(name="f1", type="file", a=f)
Input(name="f2", type="file", a=f)
Input(type="submit", value="OK", a=f)
wp.file_list = Ol(a=wp)
return wp
async def upload_demo():
return multiupload()
Created: September 28, 2022