From 23ece535606cc69fbeb826e682a57abb07202d37 Mon Sep 17 00:00:00 2001 From: admin <51248046+danton267@users.noreply.github.com> Date: Tue, 24 May 2022 18:29:39 +0100 Subject: [PATCH 1/4] refactor final --- apps/dash-svm/.gitignore | 192 ++++++++ apps/dash-svm/README.md | 1 + apps/dash-svm/app.py | 453 +++--------------- apps/dash-svm/assets/css/app.css | 99 ++++ .../dash-svm/assets/{ => css}/base-styles.css | 0 .../assets/{ => css}/custom-styles.css | 0 .../{ => assets/github}/images/animated1.gif | Bin .../{ => assets/github}/images/screenshot.png | Bin .../assets/{ => images}/dash-logo-new.png | Bin apps/dash-svm/assets/images/plotly-logo.png | Bin 0 -> 23021 bytes apps/dash-svm/requirements.txt | 15 +- apps/dash-svm/utils/components.py | 191 ++++++++ .../utils/dash_reusable_components.py | 75 --- apps/dash-svm/utils/figures.py | 2 +- apps/dash-svm/utils/helper_functions.py | 32 ++ apps/dash-svm/utils/model.py | 62 +++ 16 files changed, 644 insertions(+), 478 deletions(-) create mode 100644 apps/dash-svm/.gitignore create mode 100644 apps/dash-svm/assets/css/app.css rename apps/dash-svm/assets/{ => css}/base-styles.css (100%) rename apps/dash-svm/assets/{ => css}/custom-styles.css (100%) rename apps/dash-svm/{ => assets/github}/images/animated1.gif (100%) rename apps/dash-svm/{ => assets/github}/images/screenshot.png (100%) rename apps/dash-svm/assets/{ => images}/dash-logo-new.png (100%) create mode 100644 apps/dash-svm/assets/images/plotly-logo.png create mode 100644 apps/dash-svm/utils/components.py delete mode 100644 apps/dash-svm/utils/dash_reusable_components.py create mode 100644 apps/dash-svm/utils/helper_functions.py create mode 100644 apps/dash-svm/utils/model.py diff --git a/apps/dash-svm/.gitignore b/apps/dash-svm/.gitignore new file mode 100644 index 000000000..1f65cd953 --- /dev/null +++ b/apps/dash-svm/.gitignore @@ -0,0 +1,192 @@ +# .gitignore specifies the files that shouldn't be included +# in version control and therefore shouldn't be included when +# deploying an application to Dash Enterprise +# This is a very exhaustive list! +# This list was based off of https://github.com/github/gitignore + +# Ignore data that is generated during the runtime of an application +# This folder is used by the "Large Data" sample applications +runtime_data/ +data/ + +# Omit SQLite databases that may be produced by dash-snapshots in development +*.db + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + + +# Jupyter Notebook + +.ipynb_checkpoints +*/.ipynb_checkpoints/* + +# IPython +profile_default/ +ipython_config.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +secrets.sh + +# Spyder project settings +.spyderproject +.spyproject + +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + + +# macOS General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# History files +.Rhistory +.Rapp.history + +# Session Data files +.RData + +# User-specific files +.Ruserdata + +# Example code in package build process +*-Ex.R + +# Output files from R CMD check +/*.Rcheck/ + +# RStudio files +.Rproj.user/ + +# produced vignettes +vignettes/*.html +vignettes/*.pdf + +# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 +.httr-oauth + +# knitr and R markdown default cache directories +*_cache/ +/cache/ + +# Temporary files created by R markdown +*.utf8.md +*.knit.md + +# R Environment Variables +.Renviron + +# Linux +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# SublineText +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings \ No newline at end of file diff --git a/apps/dash-svm/README.md b/apps/dash-svm/README.md index abd26f12d..57e73c28a 100644 --- a/apps/dash-svm/README.md +++ b/apps/dash-svm/README.md @@ -64,6 +64,7 @@ An SVM is a popular Machine Learning model used in many different fields. You ca * **Matthew Chan** - *Code Review* - [@matthewchan15](https://github.com/matthewchan15) * **Yunke Xiao** - *Redesign* - [@YunkeXiao](https://github.com/YunkeXiao) * **celinehuang** - *Code Review* - [@celinehuang](https://github.com/celinehuang) +* **Daniel Anton S** - *2022 complete refactor* - [@danton267](https://github.com/danton267) ## Acknowledgments diff --git a/apps/dash-svm/app.py b/apps/dash-svm/app.py index b17a5b623..ab560cca3 100644 --- a/apps/dash-svm/app.py +++ b/apps/dash-svm/app.py @@ -1,324 +1,84 @@ -import time -import importlib - -import dash -import dash_core_components as dcc -import dash_html_components as html +from dash import Dash, dcc, Input, Output, State +import dash_bootstrap_components as dbc import numpy as np -from dash.dependencies import Input, Output, State -from sklearn.model_selection import train_test_split -from sklearn.preprocessing import StandardScaler -from sklearn import datasets -from sklearn.svm import SVC -import utils.dash_reusable_components as drc -import utils.figures as figs +from utils.components import Header, controls_first, controls_second, controls_third +from utils.model import svm_prediction + -app = dash.Dash( +app = Dash( __name__, - meta_tags=[ - {"name": "viewport", "content": "width=device-width, initial-scale=1.0"} - ], + title = "Support Vector Machine", + external_stylesheets=[dbc.themes.CYBORG], ) -app.title = "Support Vector Machine" server = app.server - -def generate_data(n_samples, dataset, noise): - if dataset == "moons": - return datasets.make_moons(n_samples=n_samples, noise=noise, random_state=0) - - elif dataset == "circles": - return datasets.make_circles( - n_samples=n_samples, noise=noise, factor=0.5, random_state=1 - ) - - elif dataset == "linear": - X, y = datasets.make_classification( - n_samples=n_samples, - n_features=2, - n_redundant=0, - n_informative=2, - random_state=2, - n_clusters_per_class=1, - ) - - rng = np.random.RandomState(2) - X += noise * rng.uniform(size=X.shape) - linearly_separable = (X, y) - - return linearly_separable - - else: - raise ValueError( - "Data type incorrectly specified. Please choose an existing dataset." - ) - - -app.layout = html.Div( +app.layout = dbc.Container( children=[ - # .container class is fixed, .container.scalable is scalable - html.Div( - className="banner", - children=[ - # Change App Name here - html.Div( - className="container scalable", - children=[ - # Change App Name here - html.H2( - id="banner-title", - children=[ - html.A( - "Support Vector Machine (SVM) Explorer", - href="https://github.com/plotly/dash-svm", - style={ - "text-decoration": "none", - "color": "inherit", - }, - ) - ], - ), - html.A( - id="banner-logo", - children=[ - html.Img(src=app.get_asset_url("dash-logo-new.png")) - ], - href="https://plot.ly/products/dash/", - ), - ], - ) - ], - ), - html.Div( - id="body", - className="container scalable", - children=[ - html.Div( - id="app-container", - # className="row", - children=[ - html.Div( - # className="three columns", - id="left-column", - children=[ - drc.Card( - id="first-card", - children=[ - drc.NamedDropdown( - name="Select Dataset", - id="dropdown-select-dataset", - options=[ - {"label": "Moons", "value": "moons"}, - { - "label": "Linearly Separable", - "value": "linear", - }, - { - "label": "Circles", - "value": "circles", - }, - ], - clearable=False, - searchable=False, - value="moons", - ), - drc.NamedSlider( - name="Sample Size", - id="slider-dataset-sample-size", - min=100, - max=500, - step=100, - marks={ - str(i): str(i) - for i in [100, 200, 300, 400, 500] - }, - value=300, - ), - drc.NamedSlider( - name="Noise Level", - id="slider-dataset-noise-level", - min=0, - max=1, - marks={ - i / 10: str(i / 10) - for i in range(0, 11, 2) - }, - step=0.1, - value=0.2, - ), - ], - ), - drc.Card( - id="button-card", - children=[ - drc.NamedSlider( - name="Threshold", - id="slider-threshold", - min=0, - max=1, - value=0.5, - step=0.01, - ), - html.Button( - "Reset Threshold", - id="button-zero-threshold", - ), - ], - ), - drc.Card( - id="last-card", - children=[ - drc.NamedDropdown( - name="Kernel", - id="dropdown-svm-parameter-kernel", - options=[ - { - "label": "Radial basis function (RBF)", - "value": "rbf", - }, - {"label": "Linear", "value": "linear"}, - { - "label": "Polynomial", - "value": "poly", - }, - { - "label": "Sigmoid", - "value": "sigmoid", - }, - ], - value="rbf", - clearable=False, - searchable=False, - ), - drc.NamedSlider( - name="Cost (C)", - id="slider-svm-parameter-C-power", - min=-2, - max=4, - value=0, - marks={ - i: "{}".format(10 ** i) - for i in range(-2, 5) - }, - ), - drc.FormattedSlider( - id="slider-svm-parameter-C-coef", - min=1, - max=9, - value=1, - ), - drc.NamedSlider( - name="Degree", - id="slider-svm-parameter-degree", - min=2, - max=10, - value=3, - step=1, - marks={ - str(i): str(i) for i in range(2, 11, 2) - }, - ), - drc.NamedSlider( - name="Gamma", - id="slider-svm-parameter-gamma-power", - min=-5, - max=0, - value=-1, - marks={ - i: "{}".format(10 ** i) - for i in range(-5, 1) - }, - ), - drc.FormattedSlider( - id="slider-svm-parameter-gamma-coef", - min=1, - max=9, - value=5, - ), - html.Div( - id="shrinking-container", - children=[ - html.P(children="Shrinking"), - dcc.RadioItems( - id="radio-svm-parameter-shrinking", - labelStyle={ - "margin-right": "7px", - "display": "inline-block", - }, - options=[ - { - "label": " Enabled", - "value": "True", - }, - { - "label": " Disabled", - "value": "False", - }, - ], - value="True", - ), - ], - ), - ], - ), - ], - ), - html.Div( - id="div-graphs", - children=dcc.Graph( - id="graph-sklearn-svm", - figure=dict( - layout=dict( - plot_bgcolor="#282b38", paper_bgcolor="#282b38" - ) - ), - ), - ), - ], - ) - ], - ), - ] + Header(app), + dbc.Row([ + dbc.Col([ + dbc.Card(controls_first), + dbc.Card(controls_second), + dbc.Card(controls_third) + ], xl=2, lg=3, sm=4, xs=12, className="control-pannel"), + dbc.Col( + dcc.Loading(dcc.Graph(id="graph-sklearn-svm")), + xl=7, lg=6, sm=4, xs=12 + ), + dbc.Col( + dbc.Row([ + dcc.Loading(dcc.Graph(id="graph-line-roc-curve", style={'height': '40vh'})), + dcc.Loading(dcc.Graph(id="graph-pie-confusion-matrix", style={'height': '40vh'})), + ]), xl=3, lg=3, sm=4, xs=12 + ), + ], className="app-body") + ], + fluid=True ) @app.callback( Output("slider-svm-parameter-gamma-coef", "marks"), - [Input("slider-svm-parameter-gamma-power", "value")], + Input("slider-svm-parameter-gamma-power", "value"), ) def update_slider_svm_parameter_gamma_coef(power): scale = 10 ** power - return {i: str(round(i * scale, 8)) for i in range(1, 10, 2)} + if power < 1: + return {i: str(round(i * scale, 5)) for i in range(1, 10, 4)} + else: + return {i: str(int(i * scale)) for i in range(1, 10, 4)} @app.callback( Output("slider-svm-parameter-C-coef", "marks"), - [Input("slider-svm-parameter-C-power", "value")], + Input("slider-svm-parameter-C-power", "value"), ) def update_slider_svm_parameter_C_coef(power): scale = 10 ** power - return {i: str(round(i * scale, 8)) for i in range(1, 10, 2)} + if power < 1: + return {i: str(round(i * scale, 2)) for i in range(1, 10, 4)} + else: + return {i: str(int(i * scale)) for i in range(1, 10, 4)} @app.callback( Output("slider-threshold", "value"), - [Input("button-zero-threshold", "n_clicks")], - [State("graph-sklearn-svm", "figure")], + Input("button-zero-threshold", "n_clicks"), + State("graph-sklearn-svm", "figure"), ) def reset_threshold_center(n_clicks, figure): if n_clicks: Z = np.array(figure["data"][0]["z"]) value = -Z.min() / (Z.max() - Z.min()) else: - value = 0.4959986285375595 + value = 0.4 return value -# Disable Sliders if kernel not in the given list @app.callback( Output("slider-svm-parameter-degree", "disabled"), - [Input("dropdown-svm-parameter-kernel", "value")], + Input("dropdown-svm-parameter-kernel", "value"), ) def disable_slider_param_degree(kernel): return kernel != "poly" @@ -326,7 +86,7 @@ def disable_slider_param_degree(kernel): @app.callback( Output("slider-svm-parameter-gamma-coef", "disabled"), - [Input("dropdown-svm-parameter-kernel", "value")], + Input("dropdown-svm-parameter-kernel", "value"), ) def disable_slider_param_gamma_coef(kernel): return kernel not in ["rbf", "poly", "sigmoid"] @@ -334,122 +94,31 @@ def disable_slider_param_gamma_coef(kernel): @app.callback( Output("slider-svm-parameter-gamma-power", "disabled"), - [Input("dropdown-svm-parameter-kernel", "value")], + Input("dropdown-svm-parameter-kernel", "value"), ) def disable_slider_param_gamma_power(kernel): return kernel not in ["rbf", "poly", "sigmoid"] @app.callback( - Output("div-graphs", "children"), - [ - Input("dropdown-svm-parameter-kernel", "value"), - Input("slider-svm-parameter-degree", "value"), - Input("slider-svm-parameter-C-coef", "value"), - Input("slider-svm-parameter-C-power", "value"), - Input("slider-svm-parameter-gamma-coef", "value"), - Input("slider-svm-parameter-gamma-power", "value"), - Input("dropdown-select-dataset", "value"), - Input("slider-dataset-noise-level", "value"), - Input("radio-svm-parameter-shrinking", "value"), - Input("slider-threshold", "value"), - Input("slider-dataset-sample-size", "value"), - ], + Output("graph-sklearn-svm", "figure"), + Output("graph-line-roc-curve", "figure"), + Output("graph-pie-confusion-matrix", "figure"), + Input("dropdown-svm-parameter-kernel", "value"), + Input("slider-svm-parameter-degree", "value"), + Input("slider-svm-parameter-C-coef", "value"), + Input("slider-svm-parameter-C-power", "value"), + Input("slider-svm-parameter-gamma-coef", "value"), + Input("slider-svm-parameter-gamma-power", "value"), + Input("dropdown-select-dataset", "value"), + Input("slider-dataset-noise-level", "value"), + Input("radio-svm-parameter-shrinking", "value"), + Input("slider-threshold", "value"), + Input("slider-dataset-sample-size", "value"), ) -def update_svm_graph( - kernel, - degree, - C_coef, - C_power, - gamma_coef, - gamma_power, - dataset, - noise, - shrinking, - threshold, - sample_size, -): - t_start = time.time() - h = 0.3 # step size in the mesh - - # Data Pre-processing - X, y = generate_data(n_samples=sample_size, dataset=dataset, noise=noise) - X = StandardScaler().fit_transform(X) - X_train, X_test, y_train, y_test = train_test_split( - X, y, test_size=0.4, random_state=42 - ) - - x_min = X[:, 0].min() - 0.5 - x_max = X[:, 0].max() + 0.5 - y_min = X[:, 1].min() - 0.5 - y_max = X[:, 1].max() + 0.5 - xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)) - - C = C_coef * 10 ** C_power - gamma = gamma_coef * 10 ** gamma_power - - if shrinking == "True": - flag = True - else: - flag = False - - # Train SVM - clf = SVC(C=C, kernel=kernel, degree=degree, gamma=gamma, shrinking=flag) - clf.fit(X_train, y_train) - - # Plot the decision boundary. For that, we will assign a color to each - # point in the mesh [x_min, x_max]x[y_min, y_max]. - if hasattr(clf, "decision_function"): - Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()]) - else: - Z = clf.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1] - - prediction_figure = figs.serve_prediction_plot( - model=clf, - X_train=X_train, - X_test=X_test, - y_train=y_train, - y_test=y_test, - Z=Z, - xx=xx, - yy=yy, - mesh_step=h, - threshold=threshold, - ) - - roc_figure = figs.serve_roc_curve(model=clf, X_test=X_test, y_test=y_test) - - confusion_figure = figs.serve_pie_confusion_matrix( - model=clf, X_test=X_test, y_test=y_test, Z=Z, threshold=threshold - ) - - return [ - html.Div( - id="svm-graph-container", - children=dcc.Loading( - className="graph-wrapper", - children=dcc.Graph(id="graph-sklearn-svm", figure=prediction_figure), - style={"display": "none"}, - ), - ), - html.Div( - id="graphs-container", - children=[ - dcc.Loading( - className="graph-wrapper", - children=dcc.Graph(id="graph-line-roc-curve", figure=roc_figure), - ), - dcc.Loading( - className="graph-wrapper", - children=dcc.Graph( - id="graph-pie-confusion-matrix", figure=confusion_figure - ), - ), - ], - ), - ] +def update_svm_graph(kernel, degree, C_coef, C_power, gamma_coef, gamma_power, dataset, noise, shrinking, threshold, sample_size): + return svm_prediction(kernel, degree, C_coef, C_power, gamma_coef, gamma_power, dataset, noise, shrinking, threshold, sample_size) -# Running the server if __name__ == "__main__": app.run_server(debug=True) diff --git a/apps/dash-svm/assets/css/app.css b/apps/dash-svm/assets/css/app.css new file mode 100644 index 000000000..96dad129e --- /dev/null +++ b/apps/dash-svm/assets/css/app.css @@ -0,0 +1,99 @@ +.container-fluid { + padding-top: 1vh; +} + +.control-pannel { + overflow-y: auto; + overflow-x: hidden; + max-height: 80vh; + position: relative; +} + +.card { + background-color: #292B37; + width: auto; + padding: 20px 30px; + border: none; + border-bottom: 1px solid #3A3D4A; +} + +.app-body { + padding: 20px 10px 10px 10px; +} + +/* Scrollbar */ + +::-webkit-scrollbar { + width: 20px; +} + +::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px grey; + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background: #2f3445; + border-radius: 10px; +} + +::-webkit-scrollbar-thumb:hover { + background: #2f3445; +} + +/* Header */ +.header { + height: 10vh; + display: flex; + background-color: #2f3445; + padding-left: 2%; + padding-right: 2%; + font-family: playfair display,sans-serif; +} +.header .header-title { + color: #9EA7C0 !important; + font-size: 5vh; +} +.header-logos { + margin-left: auto; + align-self: center; +} +.header-logos img { + margin-left: 3vh !important; + max-height: 5vh; + +} + + +/* Demo button css */ +.demo-button { + font-size: 1.5vh; + font-family: Open Sans,sans-serif; + text-decoration: none; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + border-radius: 8px; + font-weight: 700; + -webkit-padding-start: 1rem; + padding-inline-start: 1rem; + -webkit-padding-end: 1rem; + padding-inline-end: 1rem; + color: #ffffff; + letter-spacing: 1.5px; + border: solid 1.5px transparent; + box-shadow: 2px 1000px 1px #0c0c0c inset; + background-image: linear-gradient(135deg, #7A76FF, #7A76FF, #7FE4FF); + -webkit-background-size: 200% 100%; + background-size: 200% 100%; + -webkit-background-position: 99%; + background-position: 99%; + background-origin: border-box; + transition: all .4s ease-in-out; + padding-top: 1vh; + padding-bottom: 1vh; +} +.demo-button:hover { + color: #7A76FF; +} \ No newline at end of file diff --git a/apps/dash-svm/assets/base-styles.css b/apps/dash-svm/assets/css/base-styles.css similarity index 100% rename from apps/dash-svm/assets/base-styles.css rename to apps/dash-svm/assets/css/base-styles.css diff --git a/apps/dash-svm/assets/custom-styles.css b/apps/dash-svm/assets/css/custom-styles.css similarity index 100% rename from apps/dash-svm/assets/custom-styles.css rename to apps/dash-svm/assets/css/custom-styles.css diff --git a/apps/dash-svm/images/animated1.gif b/apps/dash-svm/assets/github/images/animated1.gif similarity index 100% rename from apps/dash-svm/images/animated1.gif rename to apps/dash-svm/assets/github/images/animated1.gif diff --git a/apps/dash-svm/images/screenshot.png b/apps/dash-svm/assets/github/images/screenshot.png similarity index 100% rename from apps/dash-svm/images/screenshot.png rename to apps/dash-svm/assets/github/images/screenshot.png diff --git a/apps/dash-svm/assets/dash-logo-new.png b/apps/dash-svm/assets/images/dash-logo-new.png similarity index 100% rename from apps/dash-svm/assets/dash-logo-new.png rename to apps/dash-svm/assets/images/dash-logo-new.png diff --git a/apps/dash-svm/assets/images/plotly-logo.png b/apps/dash-svm/assets/images/plotly-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..984dd57ab53b7f9fa47df219e25df91a0885613d GIT binary patch literal 23021 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.8.1 -dash>=1.0.0 - -# Additional -colorlover>=0.2.1 -numpy>=1.16.2 -pandas>=0.24.2 -scikit-learn>=0.20.3 -scipy>=1.2.1 +dash==2.4.1 +pandas==1.4.2 +scikit-learn==1.1.1 +colorlover==0.3.0 +gunicorn==20.1.0 diff --git a/apps/dash-svm/utils/components.py b/apps/dash-svm/utils/components.py new file mode 100644 index 000000000..8b3cd5c7e --- /dev/null +++ b/apps/dash-svm/utils/components.py @@ -0,0 +1,191 @@ +from dash import html, dcc +from textwrap import dedent + +def Header(app): + app_name = html.Span("Support Vector Machine (SVM) Explorer") + logo = html.Img(src=app.get_asset_url("images/plotly-logo.png")) + link = html.A(logo, href="https://plotly.com/dash/", target="_blank") + demo_link = html.A("ENTERPRISE DEMO", href="https://plotly.com/get-demo/", target="_blank", className="demo-button") + return html.Div([html.Div(app_name, className="header-title"), html.Div([demo_link, link], className="header-logos")], className="header") + + +def _omit(omitted_keys, d): + return {k: v for k, v in d.items() if k not in omitted_keys} + +def FormattedSlider(**kwargs): + return html.Div( + style=kwargs.get("style", {}), children=dcc.Slider(**_omit(["style"], kwargs)) + ) + +def NamedSlider(name, **kwargs): + return html.Div( + style={"padding": "20px 10px 25px 4px"}, + children=[ + html.P(f"{name}:"), + html.Div(style={"margin-left": "6px"}, children=dcc.Slider(**kwargs)), + ], + ) + +def NamedDropdown(name, **kwargs): + return html.Div( + style={"margin": "10px 0px"}, + children=[ + html.P(children=f"{name}:", style={"margin-left": "3px"}), + dcc.Dropdown(**kwargs), + ], + ) + + +def NamedRadioItems(name, **kwargs): + return html.Div( + style={"padding": "20px 10px 25px 4px"}, + children=[html.P(children=f"{name}:"), dcc.RadioItems(**kwargs)], + ) + + +controls_first = children=[ + NamedDropdown( + name="Select Dataset", + id="dropdown-select-dataset", + options=[ + {"label": "Moons", "value": "moons"}, + { + "label": "Linearly Separable", + "value": "linear", + }, + { + "label": "Circles", + "value": "circles", + }, + ], + clearable=False, + searchable=False, + value="moons", + ), + NamedSlider( + name="Sample Size", + id="slider-dataset-sample-size", + min=100, + max=500, + step=100, + marks={ + str(i): str(i) + for i in [100, 200, 300, 400, 500] + }, + value=300, + ), + NamedSlider( + name="Noise Level", + id="slider-dataset-noise-level", + min=0, + max=1, + marks={ + i / 10: str(i / 10) + for i in range(0, 11, 2) + }, + step=0.1, + value=0.2, + ), +] + +controls_second = [ + NamedSlider( + name="Threshold", + id="slider-threshold", + min=0, + max=1, + value=0.4, + step=0.01, + marks={ i / 10: str(i / 10) for i in range(0, 11, 2) }, + ), + html.Button( + "Reset Threshold", + id="button-zero-threshold", + ), +] + +controls_third = [ + NamedDropdown( + name="Kernel", + id="dropdown-svm-parameter-kernel", + options=[ + { + "label": "Radial basis function (RBF)", + "value": "rbf", + }, + {"label": "Linear", "value": "linear"}, + { + "label": "Polynomial", + "value": "poly", + }, + { + "label": "Sigmoid", + "value": "sigmoid", + }, + ], + value="rbf", + clearable=False, + searchable=False, + ), + NamedSlider( + name="Cost (C)", + id="slider-svm-parameter-C-power", + min=-2, + max=4, + value=0, + marks={ i: str(10 ** i) for i in range(-2, 5, 2) }, + ), + FormattedSlider( + id="slider-svm-parameter-C-coef", + min=1, + max=9, + value=1, + ), + NamedSlider( + name="Degree", + id="slider-svm-parameter-degree", + min=2, + max=10, + value=3, + step=1, + marks={ + str(i): str(i) for i in range(2, 11, 2) + }, + ), + NamedSlider( + name="Gamma", + id="slider-svm-parameter-gamma-power", + min=-5, + max=0, + value=-1, + marks={ + i: "{}".format(10 ** i) + for i in range(-5, 1, 2) + }, + ), + FormattedSlider( + id="slider-svm-parameter-gamma-coef", + min=1, + max=9, + value=5, + ), + NamedRadioItems( + name="Shrinking", + id="radio-svm-parameter-shrinking", + labelStyle={ + "margin-right": "7px", + "display": "inline-block", + }, + options=[ + { + "label": " Enabled", + "value": "True", + }, + { + "label": " Disabled", + "value": "False", + }, + ], + value="True", + ), +] \ No newline at end of file diff --git a/apps/dash-svm/utils/dash_reusable_components.py b/apps/dash-svm/utils/dash_reusable_components.py deleted file mode 100644 index e080d0876..000000000 --- a/apps/dash-svm/utils/dash_reusable_components.py +++ /dev/null @@ -1,75 +0,0 @@ -from textwrap import dedent - -import dash_core_components as dcc -import dash_html_components as html - - -# Display utility functions -def _merge(a, b): - return dict(a, **b) - - -def _omit(omitted_keys, d): - return {k: v for k, v in d.items() if k not in omitted_keys} - - -# Custom Display Components -def Card(children, **kwargs): - return html.Section(className="card", children=children, **_omit(["style"], kwargs)) - - -def FormattedSlider(**kwargs): - return html.Div( - style=kwargs.get("style", {}), children=dcc.Slider(**_omit(["style"], kwargs)) - ) - - -def NamedSlider(name, **kwargs): - return html.Div( - style={"padding": "20px 10px 25px 4px"}, - children=[ - html.P(f"{name}:"), - html.Div(style={"margin-left": "6px"}, children=dcc.Slider(**kwargs)), - ], - ) - - -def NamedDropdown(name, **kwargs): - return html.Div( - style={"margin": "10px 0px"}, - children=[ - html.P(children=f"{name}:", style={"margin-left": "3px"}), - dcc.Dropdown(**kwargs), - ], - ) - - -def NamedRadioItems(name, **kwargs): - return html.Div( - style={"padding": "20px 10px 25px 4px"}, - children=[html.P(children=f"{name}:"), dcc.RadioItems(**kwargs)], - ) - - -# Non-generic -def DemoDescription(filename, strip=False): - with open(filename, "r") as file: - text = file.read() - - if strip: - text = text.split("")[-1] - text = text.split("")[0] - - return html.Div( - className="row", - style={ - "padding": "15px 30px 27px", - "margin": "45px auto 45px", - "width": "80%", - "max-width": "1024px", - "borderRadius": 5, - "border": "thin lightgrey solid", - "font-family": "Roboto, sans-serif", - }, - children=dcc.Markdown(dedent(text)), - ) diff --git a/apps/dash-svm/utils/figures.py b/apps/dash-svm/utils/figures.py index 75c994516..90daf54f5 100644 --- a/apps/dash-svm/utils/figures.py +++ b/apps/dash-svm/utils/figures.py @@ -1,7 +1,7 @@ -import colorlover as cl import plotly.graph_objs as go import numpy as np from sklearn import metrics +import colorlover as cl def serve_prediction_plot( diff --git a/apps/dash-svm/utils/helper_functions.py b/apps/dash-svm/utils/helper_functions.py new file mode 100644 index 000000000..a372e7ede --- /dev/null +++ b/apps/dash-svm/utils/helper_functions.py @@ -0,0 +1,32 @@ +from sklearn import datasets +import numpy as np + +def generate_data(n_samples, dataset, noise): + if dataset == "moons": + return datasets.make_moons(n_samples=n_samples, noise=noise, random_state=0) + + elif dataset == "circles": + return datasets.make_circles( + n_samples=n_samples, noise=noise, factor=0.5, random_state=1 + ) + + elif dataset == "linear": + X, y = datasets.make_classification( + n_samples=n_samples, + n_features=2, + n_redundant=0, + n_informative=2, + random_state=2, + n_clusters_per_class=1, + ) + + rng = np.random.RandomState(2) + X += noise * rng.uniform(size=X.shape) + linearly_separable = (X, y) + + return linearly_separable + + else: + raise ValueError( + "Data type incorrectly specified. Please choose an existing dataset." + ) diff --git a/apps/dash-svm/utils/model.py b/apps/dash-svm/utils/model.py new file mode 100644 index 000000000..a3f26ac28 --- /dev/null +++ b/apps/dash-svm/utils/model.py @@ -0,0 +1,62 @@ +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import StandardScaler +from sklearn.svm import SVC +import numpy as np + +import utils.figures as figs +from utils.helper_functions import generate_data + +def svm_prediction(kernel, degree, C_coef, C_power, gamma_coef, gamma_power, dataset, noise, shrinking, threshold, sample_size): + h = 0.3 # step size in the mesh + + # Data Pre-processing + X, y = generate_data(n_samples=sample_size, dataset=dataset, noise=noise) + X = StandardScaler().fit_transform(X) + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.4, random_state=42 + ) + + x_min = X[:, 0].min() - 0.5 + x_max = X[:, 0].max() + 0.5 + y_min = X[:, 1].min() - 0.5 + y_max = X[:, 1].max() + 0.5 + xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)) + + C = C_coef * 10 ** C_power + gamma = gamma_coef * 10 ** gamma_power + + if shrinking == "True": + flag = True + else: + flag = False + + # Train SVM + clf = SVC(C=C, kernel=kernel, degree=degree, gamma=gamma, shrinking=flag) + clf.fit(X_train, y_train) + + # Plot the decision boundary. For that, we will assign a color to each + # point in the mesh [x_min, x_max]x[y_min, y_max]. + if hasattr(clf, "decision_function"): + Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()]) + else: + Z = clf.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1] + + prediction_figure = figs.serve_prediction_plot( + model=clf, + X_train=X_train, + X_test=X_test, + y_train=y_train, + y_test=y_test, + Z=Z, + xx=xx, + yy=yy, + mesh_step=h, + threshold=threshold, + ) + + roc_figure = figs.serve_roc_curve(model=clf, X_test=X_test, y_test=y_test) + + confusion_figure = figs.serve_pie_confusion_matrix( + model=clf, X_test=X_test, y_test=y_test, Z=Z, threshold=threshold + ) + return prediction_figure, roc_figure, confusion_figure From 583fa6c43f4411b7d50707531e837597aa404393 Mon Sep 17 00:00:00 2001 From: admin <51248046+danton267@users.noreply.github.com> Date: Tue, 24 May 2022 19:42:42 +0100 Subject: [PATCH 2/4] final --- apps/dash-svm/app.py | 2 +- apps/dash-svm/assets/css/app.css | 51 +++ apps/dash-svm/assets/css/base-styles.css | 393 ------------------ apps/dash-svm/assets/css/custom-styles.css | 446 --------------------- apps/dash-svm/utils/README.md | 7 - apps/dash-svm/utils/components.py | 14 +- 6 files changed, 60 insertions(+), 853 deletions(-) delete mode 100644 apps/dash-svm/assets/css/base-styles.css delete mode 100644 apps/dash-svm/assets/css/custom-styles.css delete mode 100644 apps/dash-svm/utils/README.md diff --git a/apps/dash-svm/app.py b/apps/dash-svm/app.py index ab560cca3..c4be4880b 100644 --- a/apps/dash-svm/app.py +++ b/apps/dash-svm/app.py @@ -15,7 +15,7 @@ app.layout = dbc.Container( children=[ - Header(app), + Header(app, "Support Vector Machine (SVM) Explorer"), dbc.Row([ dbc.Col([ dbc.Card(controls_first), diff --git a/apps/dash-svm/assets/css/app.css b/apps/dash-svm/assets/css/app.css index 96dad129e..972167699 100644 --- a/apps/dash-svm/assets/css/app.css +++ b/apps/dash-svm/assets/css/app.css @@ -1,3 +1,10 @@ +/* App style */ +body { + font-family: "Open Sans", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #a5b1cd; + background-color: #282b38; +} + .container-fluid { padding-top: 1vh; } @@ -61,7 +68,50 @@ .header-logos img { margin-left: 3vh !important; max-height: 5vh; +} +.subheader-title { + font-size: 1.5vh; +} + + +/* buttons and dropdowns */ +#button-zero-threshold { + background-color: #2f3445; + color: #a5b1cd; + border-color: gray; + border: 1px solid #bbb; + padding: 0 30px; + line-height: 38px; + border-radius: 4px; +} +.Select-control { + color: #a5b1cd; +} + +.Select { + color: #a5b1cd; +} + +.Select-menu-outer { + background-color: #2f3445; + border: 1px solid gray; +} + +.Select div { + background-color: #2f3445; +} + +.Select-menu-outer div:hover { + background-color: rgba(255, 255, 255, 0.01); +} + +.Select-value-label { + color: #a5b1cd !important; +} +.Select--single > .Select-control .Select-value, .Select-placeholder { + border: 1px solid gray; + border-radius: 4px; } @@ -96,4 +146,5 @@ } .demo-button:hover { color: #7A76FF; + background-position: 0%; } \ No newline at end of file diff --git a/apps/dash-svm/assets/css/base-styles.css b/apps/dash-svm/assets/css/base-styles.css deleted file mode 100644 index 37e03533a..000000000 --- a/apps/dash-svm/assets/css/base-styles.css +++ /dev/null @@ -1,393 +0,0 @@ -/* Table of contents -–––––––––––––––––––––––––––––––––––––––––––––––––– -- Grid -- Base Styles -- Typography -- Links -- Buttons -- Forms -- Lists -- Code -- Tables -- Spacing -- Utilities -- Clearing -- Media Queries -*/ - - -/* Grid -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -.container { - position: relative; - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 0 20px; - box-sizing: border-box; } -.column, -.columns { - width: 100%; - float: left; - box-sizing: border-box; } - -/* For devices larger than 400px */ -@media (min-width: 400px) { - .container { - width: 85%; - padding: 0; } -} - -/* For devices larger than 550px */ -@media (min-width: 550px) { - .container { - width: 80%; } - .column, - .columns { - margin-left: 0.5%; } - .column:first-child, - .columns:first-child { - margin-left: 0; } - - .one.column, - .one.columns { width: 8%; } - .two.columns { width: 16.25%; } - .three.columns { width: 22%; } - .four.columns { width: 33%; } - .five.columns { width: 39.3333333333%; } - .six.columns { width: 49.75%; } - .seven.columns { width: 56.6666666667%; } - .eight.columns { width: 66.5%; } - .nine.columns { width: 74.0%; } - .ten.columns { width: 82.6666666667%; } - .eleven.columns { width: 91.5%; } - .twelve.columns { width: 100%; margin-left: 0; } - - .one-third.column { width: 30.6666666667%; } - .two-thirds.column { width: 65.3333333333%; } - - .one-half.column { width: 48%; } - - /* Offsets */ - .offset-by-one.column, - .offset-by-one.columns { margin-left: 8.66666666667%; } - .offset-by-two.column, - .offset-by-two.columns { margin-left: 17.3333333333%; } - .offset-by-three.column, - .offset-by-three.columns { margin-left: 26%; } - .offset-by-four.column, - .offset-by-four.columns { margin-left: 34.6666666667%; } - .offset-by-five.column, - .offset-by-five.columns { margin-left: 43.3333333333%; } - .offset-by-six.column, - .offset-by-six.columns { margin-left: 52%; } - .offset-by-seven.column, - .offset-by-seven.columns { margin-left: 60.6666666667%; } - .offset-by-eight.column, - .offset-by-eight.columns { margin-left: 69.3333333333%; } - .offset-by-nine.column, - .offset-by-nine.columns { margin-left: 78.0%; } - .offset-by-ten.column, - .offset-by-ten.columns { margin-left: 86.6666666667%; } - .offset-by-eleven.column, - .offset-by-eleven.columns { margin-left: 95.3333333333%; } - - .offset-by-one-third.column, - .offset-by-one-third.columns { margin-left: 34.6666666667%; } - .offset-by-two-thirds.column, - .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } - - .offset-by-one-half.column, - .offset-by-one-half.columns { margin-left: 52%; } - -} - -/* Base Styles -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -/* NOTE -html is set to 62.5% so that all the REM measurements throughout Skeleton -are based on 10px sizing. So basically 1.5rem = 15px :) */ -html { - font-size: 62.5%; } -body { - font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ - line-height: 1.6; - font-weight: 400; - font-family: "Open Sans", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #a5b1cd; - background-color: #282b38; -} - -/* Typography -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -h1, h2, h3, h4, h5, h6 { - margin-top: 0; - margin-bottom: 0; - font-weight: 300; } -h1 { font-size: 4.5rem; line-height: 1.2; letter-spacing: -.1rem; margin-bottom: 2rem; } -h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; margin-bottom: 1.8rem; margin-top: 1.8rem;} -h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; margin-bottom: 1.5rem; margin-top: 1.5rem;} -h4 { font-size: 2.6rem; line-height: 1.35; letter-spacing: -.08rem; margin-bottom: 1.2rem; margin-top: 1.2rem;} -h5 { font-size: 2.2rem; line-height: 1.5; letter-spacing: -.05rem; margin-bottom: 0.6rem; margin-top: 0.6rem;} -h6 { font-size: 2.0rem; line-height: 1.6; letter-spacing: 0; margin-bottom: 0.75rem; margin-top: 0.75rem;} - -p { - margin-top: 0; } - - -/* Blockquotes -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -blockquote { - border-left: 4px #282b38 solid; - padding-left: 1rem; - margin-top: 2rem; - margin-bottom: 2rem; - margin-left: 0rem; -} - - -/* Links -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -a { - color: #1EAEDB; } -a:hover { - color: #0FA0CE; } - - -/* Buttons -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -.button, -button, -input[type="submit"], -input[type="reset"], -input[type="button"] { - display: inline-block; - height: 38px; - padding: 0 30px; - color: #555; - text-align: center; - font-size: 11px; - font-weight: 600; - line-height: 38px; - letter-spacing: .1rem; - text-transform: uppercase; - text-decoration: none; - white-space: nowrap; - background-color: #282b38; - border-radius: 4px; - border: 1px solid #bbb; - cursor: pointer; - box-sizing: border-box; } -.button:hover, -button:hover, -input[type="submit"]:hover, -input[type="reset"]:hover, -input[type="button"]:hover, -.button:focus, -button:focus, -input[type="submit"]:focus, -input[type="reset"]:focus, -input[type="button"]:focus { - color: #333; - border-color: #888; - outline: 0; } -.button.button-primary, -button.button-primary, -input[type="submit"].button-primary, -input[type="reset"].button-primary, -input[type="button"].button-primary { - color: #FFF; - background-color: #33C3F0; - border-color: #33C3F0; } -.button.button-primary:hover, -button.button-primary:hover, -input[type="submit"].button-primary:hover, -input[type="reset"].button-primary:hover, -input[type="button"].button-primary:hover, -.button.button-primary:focus, -button.button-primary:focus, -input[type="submit"].button-primary:focus, -input[type="reset"].button-primary:focus, -input[type="button"].button-primary:focus { - color: #FFF; - background-color: #1EAEDB; - border-color: #1EAEDB; } - - -/* Forms -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -input[type="email"], -input[type="number"], -input[type="search"], -input[type="text"], -input[type="tel"], -input[type="url"], -input[type="password"], -textarea, -select { - height: 38px; - padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ - background-color: #282b38; - border: 1px solid #D1D1D1; - border-radius: 4px; - box-shadow: none; - box-sizing: border-box; - font-family: inherit; - font-size: inherit; /*https://stackoverflow.com/questions/6080413/why-doesnt-input-inherit-the-font-from-body*/} -/* Removes awkward default styles on some inputs for iOS */ -input[type="email"], -input[type="number"], -input[type="search"], -input[type="text"], -input[type="tel"], -input[type="url"], -input[type="password"], -textarea { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; } -textarea { - min-height: 65px; - padding-top: 6px; - padding-bottom: 6px; } -input[type="email"]:focus, -input[type="number"]:focus, -input[type="search"]:focus, -input[type="text"]:focus, -input[type="tel"]:focus, -input[type="url"]:focus, -input[type="password"]:focus, -textarea:focus, -select:focus { - border: 1px solid #33C3F0; - outline: 0; } -label, -legend { - display: block; - margin-bottom: 0px; } -fieldset { - padding: 0; - border-width: 0; } -input[type="checkbox"], -input[type="radio"] { - display: inline; } -label > .label-body { - display: inline-block; - margin-left: .5rem; - font-weight: normal; } - - -/* Lists -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -ul { - list-style: circle inside; } -ol { - list-style: decimal inside; } -ol, ul { - padding-left: 0; - margin-top: 0; } -ul ul, -ul ol, -ol ol, -ol ul { - margin: 1.5rem 0 1.5rem 3rem; - font-size: 90%; } -li { - margin-bottom: 1rem; } - - -/* Tables -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -th, -td { - padding: 12px 15px; - text-align: left; - border-bottom: 1px solid #282b38; } -th:first-child, -td:first-child { - padding-left: 0; } -th:last-child, -td:last-child { - padding-right: 0; } - - -/* Spacing -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -button, -.button { - margin-bottom: 0rem; } -input, -textarea, -select, -fieldset { - margin-bottom: 0rem; } -pre, -dl, -figure, -table, -form { - margin-bottom: 0rem; } -p, -ul, -ol { - margin-bottom: 0.75rem; } - -/* Utilities -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -.u-full-width { - width: 100%; - box-sizing: border-box; } -.u-max-full-width { - max-width: 100%; - box-sizing: border-box; } -.u-pull-right { - float: right; } -.u-pull-left { - float: left; } - - -/* Misc -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -hr { - margin-top: 3rem; - margin-bottom: 3.5rem; - border-width: 0; - border-top: 1px solid #282b38; } - - -/* Clearing -–––––––––––––––––––––––––––––––––––––––––––––––––– */ - -/* Self Clearing Goodness */ -.container:after, -.row:after, -.u-cf { - content: ""; - display: table; - clear: both; } - - -/* Media Queries -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -/* -Note: The best way to structure the use of media queries is to create the queries -near the relevant code. For example, if you wanted to change the styles for buttons -on small devices, paste the mobile query code up in the buttons section and style it -there. -*/ - - -/* Larger than mobile */ -@media (min-width: 400px) {} - -/* Larger than phablet (also point when grid becomes active) */ -@media (min-width: 550px) {} - -/* Larger than tablet */ -@media (min-width: 750px) {} - -/* Larger than desktop */ -@media (min-width: 1000px) {} - -/* Larger than Desktop HD */ -@media (min-width: 1200px) {} diff --git a/apps/dash-svm/assets/css/custom-styles.css b/apps/dash-svm/assets/css/custom-styles.css deleted file mode 100644 index 892a1d5de..000000000 --- a/apps/dash-svm/assets/css/custom-styles.css +++ /dev/null @@ -1,446 +0,0 @@ -@import url('https://fonts.googleapis.com/css?family=Playfair+Display'); - -#body { - padding-bottom: 5rem; -} - -@media (max-width: 700px) { - #body { - width: 100%; - } -} - -/* Scalable Container -–––––––––––––––––––––––––––––––––––––––––––––––––– */ - -.container.scalable { - width: 95%; - max-width: none; -} - -/* Banner -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -.banner { - background-color: #2f3445; /* Machine Learning Color is orange */ - padding: 3rem 0; - width: 100%; - margin-bottom: 5rem; -} - -.banner h2 { - color: #a5b1cd; - display: inline-block; - font-family: 'Playfair Display', sans-serif; - font-size: 4rem; - line-height: 1; - text-align: center; -} - -.banner h2:hover { - color: #b4b5bf; - cursor: pointer; -} - -.banner Img { - position: relative; - float: right; - height: 5rem; - margin-top: 1.25rem -} - -@media (max-width: 1300px) { - - .banner .container.scalable { - display: flex; - flex-direction: column-reverse; - justify-content: space-between; - align-items: center; - } - - .banner h2 { - font-size: 4rem; - } - - .banner Img { - height: 8rem; - margin-bottom: 3rem; - } -} - -@media (max-width: 700px) { - .banner .container.scalable { - padding: 0 - } - - .banner Img { - height: 4rem; - margin-bottom: 2rem; - } - - .banner h2 { - font-size: 3rem; - } -} - -@media (max-width: 500px) { - .banner { - padding: 1rem 0; - margin-bottom: 1rem; - } - - .banner Img { - height: 3rem; - margin-bottom: 1rem; - } - - .banner h2 { - font-size: 2.5rem; - } -} - -/* app-container -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -@media (min-width: 951px) { - #app-container { - width: 100%; - display: flex; - flex-direction: row; - align-items: flex-start; - } -} - -/* Dropdowns -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -.Select-control { - color: #a5b1cd; -} - -.Select { - color: #a5b1cd; -} - -.Select-menu-outer { - background-color: #2f3445; - border: 1px solid gray; -} - -.Select div { - background-color: #2f3445; -} - -.Select-menu-outer div:hover { - background-color: rgba(255, 255, 255, 0.01); -} - -.Select-value-label { - color: #a5b1cd !important; -} - -.Select--single > .Select-control .Select-value, .Select-placeholder { - border: 1px solid gray; - border-radius: 4px; -} - -.card { - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - width: 80%; - padding: 2rem 0; -} - -#last-card { - border-bottom: none; -} - -.graph-title { - font-size: 2rem; - margin: 15% 0 0 25%; -} - -#button-zero-threshold { - background-color: #2f3445; - color: #a5b1cd; - border-color: gray; -} - -#button-zero-threshold:hover { - border-color: white; -} - -#button-card { - display: flex; - flex-direction: column; -} - -#first-card { - padding-top: 0; -} - -@media (max-width: 1500px) { - .rc-slider-mark-text { - font-size: 0.7vw; - } -} - -@media (max-width: 950px) { - .rc-slider-mark-text { - font-size: 1.5vw; - } -} - - -@media (max-width: 650px) { - .rc-slider-mark-text { - font-size: 2vw; - } -} - - -@media (min-width: 1301px) { - #left-column { - flex: 1 20%; - margin: 0 3rem 0 0; - max-height: 83rem; - overflow-x: hidden; - overflow-y: auto; - } - - .card { - padding-left: 2rem; - } -} - -@media (max-width: 1300px) { - #left-column { - flex: 1 20%; - margin: 0 3rem 0 0; - max-height: 70rem; - overflow-x: hidden; - overflow-y: auto; - } -} - -@media (max-width: 1200px) { - #button-zero-threshold { - font-size: 0.8rem; - padding: 0 - } -} - -@media (max-width: 950px) { - #button-zero-threshold { - font-size: 1.0rem; - padding: 0 - } -} - -@media (max-width: 950px) { - #left-column { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - margin: 0; - max-height: none; - max-width: none; - } -} - -@media (max-width: 700px) { - #first-card { - margin: 0; - padding-right: 0; - padding-left: 0; - } -} - -/* Slider -–––––––––––––––––––––––––––––––––––––––––––––––––– */ - -.rc-slider-track { - background-color: #13c6e9; -} - -.rc-slider-handle { - border: solid 2px #13c6e9; -} - -/* Left column -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -#slider-svm-parameter-C-coef { - padding: 5px 10px 25px; -} - -#slider-svm-parameter-gamma-coef { - padding: 5px 10px 25px -} - -#shrinking-container { - padding: 20px 10px 25px 4px -} - -/* Graph container -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -#svm-graph-container { - margin-top: 0.5rem; -} - -#svm-graph-container .graph-wrapper { - height: 100%; - width: 100%; -} - -@media (min-width: 951px) { - #graphs-container { - display: flex; - flex-direction: column; - align-items: stretch; - justify-content: flex-start; - } - - #graphs-container .graph-wrapper { - flex: 1 50%; - } - - #graph-line-roc-curve, #graph-pie-confusion-matrix { - height: 100%; - width: 100%; - } - - #div-graphs { - flex: 4 80%; - display: flex; - flex-direction: row; - justify-content: center; - align-items: stretch; - } - - #graph-sklearn-svm { - height: 100%; - } - - #svm-graph-container { - flex: 2 66%; - } - - #graphs-container { - flex: 1 33%; - margin-top: 0.5rem; - } -} - -@media (min-width: 1301px) { - #div-graphs { - height: 83rem; - } -} - -@media (max-width: 1300px) { - #div-graphs { - height: 70rem; - } - - .gtitle { - font-size: 1.25rem !important; - } - - .xtitle, .ytitle { - font-size: 0.9rem !important; - } -} - -@media (max-width: 950px) { - #div-graphs { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-start; - } - - #svm-graph-container { - width: 80vw; - height: 100vw; - display: flex; - flex-direction: column; - align-items: center; - padding-bottom: 4rem; - border-bottom: solid 1px rgba(255, 255, 255, 0.1); - } - - #svm-graph-container .graph-wrapper { - width: 80vw; - height: 80vw; - } - - #graphs-container { - display: flex; - flex-direction: column; - align-items: stretch; - justify-content: flex-start; - width: 90%; - max-width: 90%; - margin: 0 0 0 -1rem; - max-height: none; - } - - #graphs-container .graph-wrapper:nth-of-type(1) { - height: 40rem; - margin: 3rem 0 1rem -3rem; - width: 100%; - } - - #graphs-container .graph-wrapper:nth-of-type(2) { - height: 60rem; - margin-bottom: 5rem; - width: 100%; - } - - #graph-sklearn-svm, #graph-line-roc-curve, #graph-pie-confusion-matrix { - height: 100%; - width: 100%; - } -} - -@media (max-width: 650px) { - #graphs-container { - align-items: center; - width: 100%; - max-width: 100%; - } - - #graph-line-roc-curve { - width: 95%; - margin: 3rem 0 1rem 0; - } - - #graph-pie-confusion-matrix { - width: 95%; - margin-bottom: 5rem; - } - - #graphs-container .graph-wrapper:nth-of-type(1) { - height: 35rem; - } - - #graphs-container .graph-wrapper:nth-of-type(2) { - height: 45rem; - padding-left: 3rem; - } -} - -@media (max-width: 400px) { - #graphs-container .graph-wrapper:nth-of-type(1) { - height: 25rem; - } - - #graphs-container .graph-wrapper:nth-of-type(2) { - height: 40rem; - padding-left: 3rem; - } -} - -/* Remove Undo -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -#graph-line-roc-curve .modebar, #graph-pie-confusion-matrix .modebar { - display: none; -} diff --git a/apps/dash-svm/utils/README.md b/apps/dash-svm/utils/README.md deleted file mode 100644 index 0eac67920..000000000 --- a/apps/dash-svm/utils/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Utility Files - -## Dash Reusable Components - -Creating custom, reusable components lets you improve workflow and keep repetitions to a minimum (DRY). In this app, there are a few components that have the same pattern, but with only small differences; for example, a dropdown menu with an associated name. In these cases, reusable components were useful to keep the design of those repeated components consistent, and make the app layout less crowded. - -To read more about Reusable components, check out [this workshop by Plotly](https://dash-workshop.plot.ly/reusable-components). diff --git a/apps/dash-svm/utils/components.py b/apps/dash-svm/utils/components.py index 8b3cd5c7e..b6f45b597 100644 --- a/apps/dash-svm/utils/components.py +++ b/apps/dash-svm/utils/components.py @@ -1,12 +1,14 @@ from dash import html, dcc -from textwrap import dedent -def Header(app): - app_name = html.Span("Support Vector Machine (SVM) Explorer") +def Header(app, header, subheader=None): + left_headers = html.Div([html.Div(header, className="header-title"), html.Div(subheader, className="subheader-title")]) + logo = html.Img(src=app.get_asset_url("images/plotly-logo.png")) - link = html.A(logo, href="https://plotly.com/dash/", target="_blank") - demo_link = html.A("ENTERPRISE DEMO", href="https://plotly.com/get-demo/", target="_blank", className="demo-button") - return html.Div([html.Div(app_name, className="header-title"), html.Div([demo_link, link], className="header-logos")], className="header") + link = html.A(logo, href="https://plotly.com/dash/", target="_blank") + demo_link = html.A("ENTERPRISE DEMO", href="https://plotly.com/dash/", target="_blank", className="demo-button") + right_logos = html.Div([demo_link, link], className="header-logos") + + return html.Div([left_headers, right_logos], className="header") def _omit(omitted_keys, d): From b5e4b09256b5b2e6fc1994c6fbb8a6e14e1623e2 Mon Sep 17 00:00:00 2001 From: admin <51248046+danton267@users.noreply.github.com> Date: Tue, 24 May 2022 19:55:44 +0100 Subject: [PATCH 3/4] removed old --- apps/dash-svm/assets/images/dash-logo-new.png | Bin 4502 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/dash-svm/assets/images/dash-logo-new.png diff --git a/apps/dash-svm/assets/images/dash-logo-new.png b/apps/dash-svm/assets/images/dash-logo-new.png deleted file mode 100644 index eb700fc717bc9294bc97d337766f3fb8eb9d17e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4502 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 U Date: Tue, 24 May 2022 20:55:40 +0100 Subject: [PATCH 4/4] logo --- apps/dash-svm/utils/components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dash-svm/utils/components.py b/apps/dash-svm/utils/components.py index b6f45b597..8c6abfc90 100644 --- a/apps/dash-svm/utils/components.py +++ b/apps/dash-svm/utils/components.py @@ -5,7 +5,7 @@ def Header(app, header, subheader=None): logo = html.Img(src=app.get_asset_url("images/plotly-logo.png")) link = html.A(logo, href="https://plotly.com/dash/", target="_blank") - demo_link = html.A("ENTERPRISE DEMO", href="https://plotly.com/dash/", target="_blank", className="demo-button") + demo_link = html.A("LEARN MORE", href="https://plotly.com/dash/", target="_blank", className="demo-button") right_logos = html.Div([demo_link, link], className="header-logos") return html.Div([left_headers, right_logos], className="header")