Initial commit
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# IntelliJ project files
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
out
|
||||||
|
gen
|
||||||
|
|
||||||
|
data
|
||||||
|
venv
|
||||||
2717
assets/base.css
Normal file
2717
assets/base.css
Normal file
File diff suppressed because one or more lines are too long
1118
assets/dash-logo-new.png
Normal file
1118
assets/dash-logo-new.png
Normal file
File diff suppressed because one or more lines are too long
633
assets/style.css
Normal file
633
assets/style.css
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
/* Table of contents ––––––––––––––––––––––––––––––––––––––––––––––––––
|
||||||
|
Taken from https://codepen.io/chriddyp/pen/bWLwgP.css
|
||||||
|
- Grid
|
||||||
|
- Base Styles
|
||||||
|
- Typography
|
||||||
|
- Links
|
||||||
|
- Buttons
|
||||||
|
- Forms
|
||||||
|
- Lists
|
||||||
|
- Code
|
||||||
|
- Tables
|
||||||
|
- Spacing
|
||||||
|
- Utilities
|
||||||
|
- Clearing
|
||||||
|
- Media Queries
|
||||||
|
- Custom App CSS */
|
||||||
|
|
||||||
|
.VirtualizedSelectOption {
|
||||||
|
background-color: #1E1E1E;
|
||||||
|
color: #c3c3c3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VirtualizedSelectFocusedOption {
|
||||||
|
background-color: #5E5E5E;
|
||||||
|
color: #c3c3c3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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: 4%;
|
||||||
|
}
|
||||||
|
.column:first-child, .columns:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.one.column, .one.columns {
|
||||||
|
width: 4.66666666667%;
|
||||||
|
}
|
||||||
|
.two.columns {
|
||||||
|
width: 13.3333333333%;
|
||||||
|
}
|
||||||
|
.three.columns {
|
||||||
|
width: 22%;
|
||||||
|
}
|
||||||
|
.four.columns {
|
||||||
|
width: 30.6666666667%;
|
||||||
|
}
|
||||||
|
.five.columns {
|
||||||
|
width: 39.3333333333%;
|
||||||
|
}
|
||||||
|
.six.columns {
|
||||||
|
width: 48%;
|
||||||
|
}
|
||||||
|
.seven.columns {
|
||||||
|
width: 56.6666666667%;
|
||||||
|
}
|
||||||
|
.eight.columns {
|
||||||
|
width: 65.3333333333%;
|
||||||
|
}
|
||||||
|
.nine.columns {
|
||||||
|
width: 74.0%;
|
||||||
|
}
|
||||||
|
.ten.columns {
|
||||||
|
width: 82.6666666667%;
|
||||||
|
}
|
||||||
|
.eleven.columns {
|
||||||
|
width: 91.3333333333%;
|
||||||
|
}
|
||||||
|
.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%;
|
||||||
|
font-family: Verdana;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-size: 1.5em;
|
||||||
|
/* currently ems cause chrome bug misinterpreting rems on body element */
|
||||||
|
line-height: 1.6;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgb(50, 50, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 lightgrey solid;
|
||||||
|
padding-left: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
margin-left: 0rem;
|
||||||
|
}
|
||||||
|
/* Links –––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||||
|
a {
|
||||||
|
color: #1EAEDB;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #0FA0CE;
|
||||||
|
}
|
||||||
|
/* Buttons –––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||||
|
.rc-slider-track{
|
||||||
|
background-color: #007eff
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-slider-rail{
|
||||||
|
background-color: #505050
|
||||||
|
}
|
||||||
|
.main-svg{
|
||||||
|
font-family: "Verdana"
|
||||||
|
}
|
||||||
|
.gtitle{
|
||||||
|
font-family: "Verdana" !important
|
||||||
|
}
|
||||||
|
.g-title{
|
||||||
|
font-family: "Verdana"
|
||||||
|
}
|
||||||
|
.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: transparent;
|
||||||
|
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: #fff;
|
||||||
|
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 –––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #E1E1E1;
|
||||||
|
}
|
||||||
|
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 #E1E1E1;
|
||||||
|
}
|
||||||
|
/* 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) {
|
||||||
|
}
|
||||||
|
/* Custom App CSS Below --------------------------------- */
|
||||||
|
/* Main Layout ––––––––––––––––––––––––––––––––––––––––––––––––––*/
|
||||||
|
html, body{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #1E1E1E;
|
||||||
|
color: #d8d8d8;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-entry-point{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5 {
|
||||||
|
font-family: "Open Sans Semi Bold";
|
||||||
|
letter-spacing: 2.1px;
|
||||||
|
font-size: 21px;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-family: "Open Sans Light";
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.bg-grey{
|
||||||
|
background-color: #31302F;
|
||||||
|
}
|
||||||
|
.text-padding{
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
/* Graph Layout ––––––––––––––––––––––––––––––––––––––––––––––––––*/
|
||||||
|
.div-for-charts{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#histogram {
|
||||||
|
flex-grow: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#map-graph {
|
||||||
|
flex-grow: 2
|
||||||
|
}
|
||||||
|
.mapboxgl-canvas, .mapboxgl-map {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
/* Graph Control Objects ––––––––––––––––––––––––––––––––––––––––––––––––––*/
|
||||||
|
.div-user-controls {
|
||||||
|
padding-left: 55px;
|
||||||
|
padding-top: 64px;
|
||||||
|
}
|
||||||
|
.div-for-dropdown {
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
.div-for-slider {
|
||||||
|
width: 97%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
height: 50px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
.Select-control, .Select-menu-outer, .Select-multi-value-wrapper, .select-up, .is-open .Select-control {
|
||||||
|
background-color: #1E1E1E;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#modelselector .Select-control{
|
||||||
|
max-height: 82px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 0px 0px -3px 0px;
|
||||||
|
border: 0.5px solid #dbdbdb44;
|
||||||
|
}
|
||||||
|
.Select-control{
|
||||||
|
border: 0.5px solid #dbdbdb44;
|
||||||
|
}
|
||||||
|
.has-value.Select--single>.Select-control .Select-value .Select-value-label, .has-value.is-pseudo-focused.Select--single>.Select-control .Select-value .Select-value-label {
|
||||||
|
color: white;
|
||||||
|
background: #1E1E1E;
|
||||||
|
background-color: #1E1E1E;
|
||||||
|
}
|
||||||
|
._dash-app-content {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.has-value.Select--single>.Select-control .Select-value .Select-value-label, .has-value.is-pseudo-focused.Select--single>.Select-control .Select-value .Select-value-labeln {
|
||||||
|
color: #d8d8d8;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Input.DateInput_1 {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
#date {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
font-family: "Open Sans Light";
|
||||||
|
font-weight: 400;
|
||||||
|
color: #dbdbdb;
|
||||||
|
border: 0.5px solid #dbdbdb44;
|
||||||
|
}
|
||||||
|
|
||||||
|
#date-picker{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._dash-undo-redo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DateInput.DateInput_1{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SingleDatePickerInput__withBorder{
|
||||||
|
border:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DateInput.DateInput_1 {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SingleDatePickerInput__withBorder {
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #dbdbdb;
|
||||||
|
background: #1e1e1e;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SingleDatePickerInput.SingleDatePickerInput_1.SingleDatePickerInput__withBorder.SingleDatePickerInput__withBorder_2 {
|
||||||
|
border: 0.0px solid #dbdbdb;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SingleDatePicker_1 {
|
||||||
|
border: 0.0px solid #dbdbdb;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SingleDatePicker_picker, .SingleDatePicker_picker_1, .SingleDatePicker_picker__directionLeft,
|
||||||
|
.SingleDatePicker_picker__directionLeft_2{
|
||||||
|
filter:invert(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For Mobile Phones and small screens ––––––––––––––––––––––––––––––––––––––––––––––––––*/
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.four, .eight {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
h1, h2, h3, p {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
display: block;
|
||||||
|
margin: 0px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.div-for-charts {
|
||||||
|
padding: 0px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.div-user-controls {
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
.side-by-side{
|
||||||
|
display: inline-block;
|
||||||
|
width: 48%;
|
||||||
|
}
|
||||||
|
.side-by-side-right{
|
||||||
|
display: inline-block;
|
||||||
|
width: 48%;
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
.div-for-charts{
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* width */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px !important;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Track */
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #1e1e1e !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle */
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle on hover */
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #d8d8d870 !important;
|
||||||
|
}
|
||||||
115
stockdash_main.py
Normal file
115
stockdash_main.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
import stockdash_render
|
||||||
|
import yfinance as yf
|
||||||
|
|
||||||
|
API_KEY = '44ced5e44c50543745b1d89fce8cd93a'
|
||||||
|
api_key = "?apikey=" + API_KEY
|
||||||
|
api_kpi_url = "https://financialmodelingprep.com/api/v3/key-metrics/"
|
||||||
|
api_batch_stock_price_url = "https://financialmodelingprep.com/api/v3/quote/"
|
||||||
|
|
||||||
|
data_dir = 'data'
|
||||||
|
file_symbol = os.path.join(data_dir, 'symbols.json')
|
||||||
|
file_kpi = os.path.join(data_dir, 'comp_kpi.csv')
|
||||||
|
file_div = os.path.join(data_dir, 'comp_div.csv')
|
||||||
|
file_rec = os.path.join(data_dir, 'comp_rec.csv')
|
||||||
|
file_his = os.path.join(data_dir, 'comp_his.csv')
|
||||||
|
|
||||||
|
sym_exclude = []
|
||||||
|
used_columns = ['symbol', 'shortName', 'sector', 'industry', 'country', 'marketCap', 'enterpriseValue', 'dividendRate',
|
||||||
|
'trailingPE', 'forwardPE', 'enterpriseToEbitda', 'shortRatio']
|
||||||
|
|
||||||
|
|
||||||
|
def load_symbols():
|
||||||
|
symbols = []
|
||||||
|
with open(file_symbol) as json_file:
|
||||||
|
data = json.load(json_file)
|
||||||
|
for sym in data:
|
||||||
|
if 'exchange' in sym and sym['exchange'] == 'Nasdaq Global Select' and sym['symbol'] not in sym_exclude:
|
||||||
|
symbols.append(sym['symbol'])
|
||||||
|
return symbols
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(symbols):
|
||||||
|
tickers = yf.Tickers(' '.join(symbols))
|
||||||
|
|
||||||
|
if os.path.exists(file_kpi) and os.path.exists(file_div) and os.path.exists(file_rec) and os.path.exists(file_his):
|
||||||
|
print("Found cached files. Loading cache...")
|
||||||
|
kpi_data = pd.read_csv(file_kpi)
|
||||||
|
print("%s KPIs loaded..." % len(kpi_data))
|
||||||
|
div_data = pd.read_csv(file_div)
|
||||||
|
print("%s Dividends loaded..." % len(div_data))
|
||||||
|
rec_data = pd.read_csv(file_rec)
|
||||||
|
print("%s Recommendations loaded..." % len(rec_data))
|
||||||
|
his_data = pd.read_csv(file_his)
|
||||||
|
print("%s Price History loaded..." % len(his_data))
|
||||||
|
else:
|
||||||
|
kpi_data, div_data, rec_data, his_data = pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), pd.DataFrame()
|
||||||
|
i = 1
|
||||||
|
for ticker in tickers.tickers:
|
||||||
|
try:
|
||||||
|
info = ticker.info
|
||||||
|
print("%s/%s Querying data for ticker %s" % (i, len(symbols), info['symbol']))
|
||||||
|
kpi_data = kpi_data.append(info, ignore_index=True)
|
||||||
|
|
||||||
|
div = ticker.dividends.to_frame().reset_index()
|
||||||
|
div.insert(0, 'Symbol', info['symbol'])
|
||||||
|
div_data = div_data.append(div, ignore_index=True)
|
||||||
|
|
||||||
|
rec = ticker.recommendations.reset_index()
|
||||||
|
rec.insert(0, 'Symbol', info['symbol'])
|
||||||
|
rec_data = rec_data.append(rec, ignore_index=True)
|
||||||
|
|
||||||
|
his = ticker.history(period='5y').reset_index()
|
||||||
|
his.insert(0, 'Symbol', info['symbol'])
|
||||||
|
his_data = his_data.append(his, ignore_index=True)
|
||||||
|
i += 1
|
||||||
|
except Exception:
|
||||||
|
print("Error occured when quering %s - skipping this entry")
|
||||||
|
continue
|
||||||
|
|
||||||
|
kpi_data = kpi_data[['symbol'] + [col for col in kpi_data.columns if col != 'symbol']]
|
||||||
|
|
||||||
|
rec_data['Date'] = pd.to_datetime(rec_data['Date'])
|
||||||
|
|
||||||
|
his_data['priceMA50'] = his_data['Close'].rolling(window=50).mean()
|
||||||
|
his_data['priceMA200'] = his_data['Close'].rolling(window=200).mean()
|
||||||
|
his_data['diffMA50_200'] = his_data['priceMA50'] - his_data['priceMA200']
|
||||||
|
|
||||||
|
kpi_data.to_csv(file_kpi)
|
||||||
|
div_data.to_csv(file_div)
|
||||||
|
rec_data.to_csv(file_rec)
|
||||||
|
his_data.to_csv(file_his)
|
||||||
|
|
||||||
|
return kpi_data, div_data, rec_data, his_data
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
start_time = time.time()
|
||||||
|
print("----- Starting STOCKDASH @ %s -----" % datetime.fromtimestamp(start_time))
|
||||||
|
used_symbols = load_symbols()
|
||||||
|
|
||||||
|
print("%s symbols loaded from file" % len(used_symbols))
|
||||||
|
kpi_data, div_data, rec_data, his_data = get_data(used_symbols[:100])
|
||||||
|
|
||||||
|
# Modify Recommendation Data
|
||||||
|
rec_data_mod = pd.concat([rec_data, pd.get_dummies(rec_data['To Grade'], prefix='grade')], axis=1)
|
||||||
|
rec_data_mod.drop(['To Grade', 'From Grade', 'Action'], axis=1, inplace=True)
|
||||||
|
rec_data_mod['Date'] = pd.to_datetime(rec_data_mod['Date'])
|
||||||
|
df2 = rec_data_mod.groupby([pd.Grouper(key='Date', freq='Y'), pd.Grouper('Symbol')]).agg(['sum'])
|
||||||
|
|
||||||
|
df2['Positive'] = df2['grade_Buy'] + df2['grade_Outperform'] + df2['grade_Market Outperform'] + df2[
|
||||||
|
'grade_Overweight'] + df2['grade_Positive'] + df2['grade_Strong Buy']
|
||||||
|
df2['Neutral'] = df2['grade_Equal-Weight'] + df2['grade_Hold'] + df2['grade_Neutral']
|
||||||
|
df2['Negative'] = df2['grade_Market Underperform'] + df2['grade_Reduce'] + df2['grade_Sell'] + df2[
|
||||||
|
'grade_Underweight']
|
||||||
|
|
||||||
|
columns = ['Positive', 'Neutral', 'Negative']
|
||||||
|
rec_data_mod = df2[columns]
|
||||||
|
|
||||||
|
print("Data loaded after %ss" % (time.time()-start_time))
|
||||||
|
stockdash_render.load_dash(kpi_data[used_columns], rec_data_mod, div_data, his_data)
|
||||||
288
stockdash_render.py
Normal file
288
stockdash_render.py
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
import dash_table
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
import dash_core_components as dcc
|
||||||
|
import dash_html_components as html
|
||||||
|
from dash import Dash
|
||||||
|
from dash_table.Format import Format
|
||||||
|
from dash.dependencies import Input, Output
|
||||||
|
|
||||||
|
colorway = ["#9a58cc", '#FF4F00', '#375CB1', '#FF7400', '#FFF400', '#FF0056']
|
||||||
|
PAGE_SIZE = 20
|
||||||
|
|
||||||
|
|
||||||
|
def load_dash(comp_kpi, rec_data_mod, div_data, his_data):
|
||||||
|
start_time = time.time()
|
||||||
|
print(" -- Rendering STOCKDASH @ %s -----" % datetime.fromtimestamp(start_time))
|
||||||
|
|
||||||
|
app = Dash(__name__)
|
||||||
|
|
||||||
|
app.layout = html.Div(children=[
|
||||||
|
html.Div(className='row',
|
||||||
|
children=[html.Div(className='three columns div-user-controls',
|
||||||
|
children=[
|
||||||
|
html.H2('STOCKDASH'),
|
||||||
|
html.P('''Visualising data with Plotly - Dash'''),
|
||||||
|
html.P('''Pick one or more KPIs from the dropdown below.'''),
|
||||||
|
html.Div(
|
||||||
|
className='div-for-dropdown',
|
||||||
|
children=[
|
||||||
|
dcc.Dropdown(id='stockselector',
|
||||||
|
options=[{'label': i, 'value': i} for i in
|
||||||
|
comp_kpi._get_numeric_data().columns],
|
||||||
|
multi=True,
|
||||||
|
value=[comp_kpi._get_numeric_data().columns[0]],
|
||||||
|
style={'backgroundColor': '#1E1E1E'},
|
||||||
|
className='stockselector')
|
||||||
|
],
|
||||||
|
style={'color': '#1E1E1E'})
|
||||||
|
]),
|
||||||
|
html.Div(className='nine columns div-for-charts bg-grey',
|
||||||
|
style={'padding': 0},
|
||||||
|
children=[
|
||||||
|
dash_table.DataTable(
|
||||||
|
id='company-kpi-data',
|
||||||
|
columns=
|
||||||
|
[{"name": i, "id": i, 'deletable': True, 'type': 'numeric',
|
||||||
|
'format': Format(group=',')} if i in comp_kpi._get_numeric_data().columns
|
||||||
|
else {"name": i, "id": i, 'deletable': True} for i in comp_kpi.columns],
|
||||||
|
style_as_list_view=True,
|
||||||
|
style_data_conditional=[{
|
||||||
|
'if': {'column_editable': False},
|
||||||
|
'backgroundColor': 'rgba(50, 50, 50, 0.5)',
|
||||||
|
'textAlign': 'left',
|
||||||
|
'color': 'white',
|
||||||
|
'padding': 7
|
||||||
|
}],
|
||||||
|
style_filter_conditional=[{
|
||||||
|
'if': {'column_editable': False},
|
||||||
|
'backgroundColor': 'rgba(40, 40, 40,0.5)',
|
||||||
|
'textAlign': 'left',
|
||||||
|
'color': 'white'
|
||||||
|
}],
|
||||||
|
style_header_conditional=[{
|
||||||
|
'if': {'column_editable': False},
|
||||||
|
'backgroundColor': 'rgba(30, 30, 30,0.5)',
|
||||||
|
'textAlign': 'left',
|
||||||
|
'fontWeight': 'bold',
|
||||||
|
'color': 'white'
|
||||||
|
}],
|
||||||
|
page_current=0,
|
||||||
|
page_size=PAGE_SIZE,
|
||||||
|
page_action='custom',
|
||||||
|
|
||||||
|
filter_action='custom',
|
||||||
|
filter_query='',
|
||||||
|
|
||||||
|
sort_action='custom',
|
||||||
|
sort_mode='multi',
|
||||||
|
sort_by=[]
|
||||||
|
),
|
||||||
|
dcc.Graph(
|
||||||
|
id='bar-chart-marketcap',
|
||||||
|
className='bg-grey',
|
||||||
|
hoverData={'points': [{'x': 'AAPL'}]},
|
||||||
|
animate=True),
|
||||||
|
dcc.Graph(
|
||||||
|
id='timeseries-chart-price',
|
||||||
|
className='bg-grey',
|
||||||
|
config={'displayModeBar': False},
|
||||||
|
animate=False),
|
||||||
|
dcc.Graph(
|
||||||
|
id='recom-bar-chart',
|
||||||
|
className='bg-grey',
|
||||||
|
config={'displayModeBar': False},
|
||||||
|
animate=True)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
@app.callback(Output('bar-chart-marketcap', 'figure'),
|
||||||
|
[Input('company-kpi-data', 'data'),
|
||||||
|
Input('stockselector', 'value')])
|
||||||
|
def update_graph(data, selected_columns):
|
||||||
|
used_symbols = [x['symbol'] for x in data]
|
||||||
|
|
||||||
|
figure = go.Figure(
|
||||||
|
layout=go.Layout(
|
||||||
|
colorway=colorway,
|
||||||
|
template='plotly_dark',
|
||||||
|
paper_bgcolor='rgba(0, 0, 0, 0)',
|
||||||
|
plot_bgcolor='rgba(0, 0, 0, 0)',
|
||||||
|
margin={'b': 15},
|
||||||
|
hovermode='x',
|
||||||
|
autosize=True,
|
||||||
|
title={'text': 'Market Data', 'font': {'color': 'white'}, 'x': 0.5}
|
||||||
|
))
|
||||||
|
|
||||||
|
val = dict()
|
||||||
|
val["xaxis"] = dict(domain=[0.15, 0.85])
|
||||||
|
for i, column in enumerate(selected_columns):
|
||||||
|
i += 1
|
||||||
|
figure.add_trace(go.Bar(name=column,
|
||||||
|
x=used_symbols,
|
||||||
|
y=[x[column] for x in data],
|
||||||
|
yaxis='y' + str(i), offsetgroup=i))
|
||||||
|
|
||||||
|
val["yaxis%s" % i] = dict(
|
||||||
|
title=column,
|
||||||
|
titlefont=dict(color=colorway[i - 1]),
|
||||||
|
tickfont=dict(color=colorway[i - 1]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if i == 2:
|
||||||
|
val["yaxis2"].update(dict(
|
||||||
|
anchor="x",
|
||||||
|
overlaying="y",
|
||||||
|
side="right"
|
||||||
|
))
|
||||||
|
elif i == 3:
|
||||||
|
val["yaxis3"].update(dict(
|
||||||
|
anchor="free",
|
||||||
|
overlaying="y",
|
||||||
|
side="left",
|
||||||
|
position=0.05
|
||||||
|
))
|
||||||
|
elif i == 4:
|
||||||
|
val["yaxis4"].update(dict(
|
||||||
|
anchor="free",
|
||||||
|
overlaying="y",
|
||||||
|
side="right",
|
||||||
|
position=0.95
|
||||||
|
))
|
||||||
|
|
||||||
|
figure.update_layout(val)
|
||||||
|
figure.update_yaxes(
|
||||||
|
showgrid=True, zeroline=True, zerolinewidth=1, zerolinecolor='White',
|
||||||
|
)
|
||||||
|
|
||||||
|
return figure
|
||||||
|
|
||||||
|
@app.callback(Output('recom-bar-chart', 'figure'),
|
||||||
|
[Input('company-kpi-data', 'data')])
|
||||||
|
def update_graph(data):
|
||||||
|
used_symbols = [x['symbol'] for x in data]
|
||||||
|
|
||||||
|
df = rec_data_mod.loc['2020-12-31'].reset_index()
|
||||||
|
df_tmp = df.loc[df['Symbol'].isin(used_symbols)]
|
||||||
|
|
||||||
|
figure = go.Figure(layout=go.Layout(
|
||||||
|
colorway=colorway,
|
||||||
|
template='plotly_dark',
|
||||||
|
paper_bgcolor='rgba(0, 0, 0, 0)',
|
||||||
|
plot_bgcolor='rgba(0, 0, 0, 0)',
|
||||||
|
margin={'b': 15},
|
||||||
|
hovermode='x',
|
||||||
|
autosize=True,
|
||||||
|
title={'text': 'Recommendation Data', 'font': {'color': 'white'}, 'x': 0.5},
|
||||||
|
barmode='stack'
|
||||||
|
))
|
||||||
|
|
||||||
|
figure.add_trace(go.Bar(x=used_symbols, y=df_tmp['Positive'].tolist(), name='Positive Outlook', marker_color='#41B3A3'))
|
||||||
|
figure.add_trace(go.Bar(x=used_symbols, y=df_tmp['Neutral'].tolist(), name='Neutral Outlook', marker_color='#E8A87C'))
|
||||||
|
figure.add_trace(go.Bar(x=used_symbols, y=df_tmp['Negative'].tolist(), name='Negative Outlook', marker_color='#E27D60'))
|
||||||
|
|
||||||
|
return figure
|
||||||
|
|
||||||
|
@app.callback(Output('timeseries-chart-price', 'figure'),
|
||||||
|
[Input('bar-chart-marketcap', 'hoverData')])
|
||||||
|
def update_graph(hoverData):
|
||||||
|
trace1 = []
|
||||||
|
columns = ['Close', 'priceMA50', 'priceMA200']
|
||||||
|
df_sub = his_data[his_data['Symbol'] == hoverData['points'][0]['x']]
|
||||||
|
|
||||||
|
for column in columns:
|
||||||
|
trace1.append(go.Scatter(x=df_sub['Date'],
|
||||||
|
y=df_sub[column],
|
||||||
|
mode='lines',
|
||||||
|
opacity=0.7,
|
||||||
|
name=hoverData['points'][0]['x'] + "-" + column,
|
||||||
|
textposition='bottom center'))
|
||||||
|
|
||||||
|
traces = [trace1]
|
||||||
|
data = [val for sublist in traces for val in sublist]
|
||||||
|
figure = {'data': data,
|
||||||
|
'layout': go.Layout(
|
||||||
|
colorway=colorway,
|
||||||
|
template='plotly_dark',
|
||||||
|
paper_bgcolor='rgba(0, 0, 0, 0)',
|
||||||
|
plot_bgcolor='rgba(0, 0, 0, 0)',
|
||||||
|
margin={'b': 15},
|
||||||
|
hovermode='x',
|
||||||
|
autosize=True,
|
||||||
|
|
||||||
|
title={'text': 'Stock Prices', 'font': {'color': 'white'}, 'x': 0.5},
|
||||||
|
xaxis={'range': [df_sub['Date'].min(), df_sub['Date'].max()]},
|
||||||
|
yaxis={'range': [0, df_sub['Close'].max() + df_sub['Close'].max() / 10]}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return figure
|
||||||
|
|
||||||
|
def split_filter_part(filter_part):
|
||||||
|
operators = [['ge ', '>='],
|
||||||
|
['le ', '<='],
|
||||||
|
['lt ', '<'],
|
||||||
|
['gt ', '>'],
|
||||||
|
['ne ', '!='],
|
||||||
|
['eq ', '='],
|
||||||
|
['contains '],
|
||||||
|
['datestartswith ']]
|
||||||
|
|
||||||
|
for operator_type in operators:
|
||||||
|
for operator in operator_type:
|
||||||
|
if operator in filter_part:
|
||||||
|
name_part, value_part = filter_part.split(operator, 1)
|
||||||
|
name = name_part[name_part.find('{') + 1: name_part.rfind('}')]
|
||||||
|
|
||||||
|
value_part = value_part.strip()
|
||||||
|
v0 = value_part[0]
|
||||||
|
|
||||||
|
if v0 == value_part[-1] and v0 in ("'", '"', '`'):
|
||||||
|
value = value_part[1: -1].replace('\\' + v0, v0)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = float(value_part)
|
||||||
|
except ValueError:
|
||||||
|
value = value_part
|
||||||
|
|
||||||
|
return name, operator_type[0].strip(), value
|
||||||
|
return [None] * 3
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output('company-kpi-data', "data"),
|
||||||
|
Input('company-kpi-data', "page_current"),
|
||||||
|
Input('company-kpi-data', "page_size"),
|
||||||
|
Input('company-kpi-data', "sort_by"),
|
||||||
|
Input('company-kpi-data', 'filter_query'))
|
||||||
|
def update_table(page_current, page_size, sort_by, filter):
|
||||||
|
filtering_expressions = filter.split(' && ')
|
||||||
|
dff = comp_kpi
|
||||||
|
for filter_part in filtering_expressions:
|
||||||
|
col_name, operator, filter_value = split_filter_part(filter_part)
|
||||||
|
|
||||||
|
if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
|
||||||
|
dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
|
||||||
|
elif operator == 'contains':
|
||||||
|
dff = dff.loc[dff[col_name].str.contains(filter_value)]
|
||||||
|
elif operator == 'datestartswith':
|
||||||
|
dff = dff.loc[dff[col_name].str.startswith(filter_value)]
|
||||||
|
|
||||||
|
if len(sort_by):
|
||||||
|
dff = dff.sort_values(
|
||||||
|
[col['column_id'] for col in sort_by],
|
||||||
|
ascending=[
|
||||||
|
col['direction'] == 'asc'
|
||||||
|
for col in sort_by
|
||||||
|
],
|
||||||
|
inplace=False
|
||||||
|
)
|
||||||
|
|
||||||
|
page = page_current
|
||||||
|
size = page_size
|
||||||
|
return dff.iloc[page * size: (page + 1) * size].to_dict('records')
|
||||||
|
|
||||||
|
print("Rendering loaded after %ss" % (time.time()-start_time))
|
||||||
|
app.run_server(debug=True)
|
||||||
Reference in New Issue
Block a user