commit 8e391a2597ab4409b0fa0c97d0c9d04035e13d1f Author: Dennis Thiessen Date: Sat Dec 12 15:23:59 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55b5a88 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# IntelliJ project files +.idea +*.iml +out +gen + +data +venv diff --git a/assets/base.css b/assets/base.css new file mode 100644 index 0000000..931563c --- /dev/null +++ b/assets/base.css @@ -0,0 +1,2717 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dash-sample-apps/base.css at master · plotly/dash-sample-apps · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ +
+

+ + + / + + dash-sample-apps + + +

+ + +
+ + + +
+ + +
+ + +
+
+ + + + + + + Permalink + + + + + +
+ +
+
+ + + master + + + + +
+ + + +
+
+
+ +
+ + + + Go to file + + +
+ + +
+ +
+ + + +
+ +
+
+ + @ronniebugia + + +
+ + Latest commit + 5facc76 + Jul 5, 2019 + + + + + + History + + +
+
+ +
+ +
+
+ + + 1 + + contributor + + +
+ +

+ Users who have contributed to this file +

+
+ + + + + + + + +
+
+
+
+ + + + + + +
+ +
+
+ + 392 lines (390 sloc) + + 11.8 KB +
+ +
+ +
+ Raw + Blame +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
/* 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 */
+
+
/* 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%;
}
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: 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 –––––––––––––––––––––––––––––––––––––––––––––––––– */
.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) {
}
+ + + +
+ +
+ + + + +
+ + +
+ + +
+
+ + + + +
+
+ +
+
+ +
+ + + + + + +
+ + + You can’t perform that action at this time. +
+ + + + + + + + + + + + + diff --git a/assets/dash-logo-new.png b/assets/dash-logo-new.png new file mode 100644 index 0000000..7919522 --- /dev/null +++ b/assets/dash-logo-new.png @@ -0,0 +1,1118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dash-sample-apps/dash-logo-new.png at master · plotly/dash-sample-apps · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ +
+

+ + + / + + dash-sample-apps + + +

+ + +
+ + + +
+ + +
+ + +
+
+ + + + + + + Permalink + + + + + +
+ +
+
+ + + master + + + + +
+ + + +
+
+
+ +
+ + + + Go to file + + +
+ + +
+ +
+ + + +
+ +
+
+ + @ronniebugia + + +
+ + Latest commit + 5facc76 + Jul 5, 2019 + + + + + + History + + +
+
+ +
+ +
+
+ + + 1 + + contributor + + +
+ +

+ Users who have contributed to this file +

+
+ + + + + + + + +
+
+
+
+ + + + + + +
+ +
+
+ + 4.4 KB +
+ +
+ +
+ Download +
+ +
+ + + + +
+ +
+
+
+ + + +
+
+ dash-logo-new.png +
+
+ +
+ + + + +
+ + +
+ + +
+
+ + + + +
+
+ +
+
+ +
+ + + + + + +
+ + + You can’t perform that action at this time. +
+ + + + + + + + + + + + + diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..7c58688 --- /dev/null +++ b/assets/style.css @@ -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; +} diff --git a/stockdash_main.py b/stockdash_main.py new file mode 100644 index 0000000..3ba1c5f --- /dev/null +++ b/stockdash_main.py @@ -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) diff --git a/stockdash_render.py b/stockdash_render.py new file mode 100644 index 0000000..d18f792 --- /dev/null +++ b/stockdash_render.py @@ -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)