Reworked statistics with HoloViews/Bokeh
This commit is contained in:
parent
67a6be0e3c
commit
7301e2a401
14 changed files with 922 additions and 11 deletions
|
@ -2,6 +2,8 @@ from django import template
|
|||
|
||||
from io import BytesIO
|
||||
|
||||
import holoviews as hv
|
||||
|
||||
import base64
|
||||
|
||||
register = template.Library()
|
||||
|
@ -12,3 +14,17 @@ def pildata(image):
|
|||
image.save(data, "JPEG")
|
||||
content = base64.b64encode(data.getvalue()).decode("UTF-8")
|
||||
return f"data:img/jpeg;base64,{content}"
|
||||
|
||||
@register.simple_tag
|
||||
def hvhtml(hvobject):
|
||||
renderer = hv.renderer('bokeh')
|
||||
html = renderer.html(hvobject, resources="inline")
|
||||
|
||||
html = html.replace("http://localhost:5006/static/extensions/panel/css", "/static/frontend/vendor/panel")
|
||||
|
||||
return html
|
||||
|
||||
@register.simple_tag
|
||||
def hvdata(hvobject):
|
||||
html = hvhtml(hvobject)
|
||||
return f"data:text/html;charset=utf-8,{html}"
|
8
common/templatetags/request.py
Normal file
8
common/templatetags/request.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def querystring(context, **kwargs):
|
||||
string = context["request"].GET.urlencode()
|
||||
return f"?{string}" if string else ""
|
136
frontend/static/frontend/vendor/panel/alerts.css
vendored
Normal file
136
frontend/static/frontend/vendor/panel/alerts.css
vendored
Normal file
|
@ -0,0 +1,136 @@
|
|||
.bk.alert {
|
||||
padding: 0.75rem 1.25rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25rem;
|
||||
/* Don't set margin because that will not render correctly! */
|
||||
/* margin-bottom: 1rem; */
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.bk.alert a {
|
||||
color: rgb(11, 46, 19); /* #002752; */
|
||||
font-weight: 700;
|
||||
text-decoration: rgb(11, 46, 19);
|
||||
text-decoration-color: rgb(11, 46, 19);
|
||||
text-decoration-line: none;
|
||||
text-decoration-style: solid;
|
||||
text-decoration-thickness: auto;
|
||||
}
|
||||
.bk.alert a:hover {
|
||||
color: rgb(11, 46, 19);
|
||||
font-weight: 700;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.bk.alert-primary {
|
||||
color: #004085;
|
||||
background-color: #cce5ff;
|
||||
border-color: #b8daff;
|
||||
}
|
||||
.bk.alert-primary hr {
|
||||
border-top-color: #9fcdff;
|
||||
}
|
||||
|
||||
.bk.alert-secondary {
|
||||
color: #383d41;
|
||||
background-color: #e2e3e5;
|
||||
border-color: #d6d8db;
|
||||
}
|
||||
.bk.alert-secondary hr {
|
||||
border-top-color: #c8cbcf;
|
||||
}
|
||||
|
||||
.bk.alert-success {
|
||||
color: #155724;
|
||||
background-color: #d4edda;
|
||||
border-color: #c3e6cb;
|
||||
}
|
||||
|
||||
.bk.alert-success hr {
|
||||
border-top-color: #b1dfbb;
|
||||
}
|
||||
|
||||
.bk.alert-info {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
}
|
||||
.bk.alert-info hr {
|
||||
border-top-color: #abdde5;
|
||||
}
|
||||
|
||||
.bk.alert-warning {
|
||||
color: #856404;
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffeeba;
|
||||
}
|
||||
|
||||
.bk.alert-warning hr {
|
||||
border-top-color: #ffe8a1;
|
||||
}
|
||||
|
||||
.bk.alert-danger {
|
||||
color: #721c24;
|
||||
background-color: #f8d7da;
|
||||
border-color: #f5c6cb;
|
||||
}
|
||||
.bk.alert-danger hr {
|
||||
border-top-color: #f1b0b7;
|
||||
}
|
||||
|
||||
.bk.alert-light {
|
||||
color: #818182;
|
||||
background-color: #fefefe;
|
||||
border-color: #fdfdfe;
|
||||
}
|
||||
.bk.alert-light hr {
|
||||
border-top-color: #ececf6;
|
||||
}
|
||||
|
||||
.bk.alert-dark {
|
||||
color: #1b1e21;
|
||||
background-color: #d6d8d9;
|
||||
border-color: #c6c8ca;
|
||||
}
|
||||
.bk.alert-dark hr {
|
||||
border-top-color: #b9bbbe;
|
||||
}
|
||||
|
||||
|
||||
/* adjfæl */
|
||||
|
||||
.bk.alert-primary a {
|
||||
color: #002752;
|
||||
}
|
||||
|
||||
.bk.alert-secondary a {
|
||||
color: #202326;
|
||||
}
|
||||
|
||||
|
||||
.bk.alert-success a {
|
||||
color: #0b2e13;
|
||||
}
|
||||
|
||||
|
||||
.bk.alert-info a {
|
||||
color: #062c33;
|
||||
}
|
||||
|
||||
|
||||
.bk.alert-warning a {
|
||||
color: #533f03;
|
||||
}
|
||||
|
||||
|
||||
.bk.alert-danger a {
|
||||
color: #491217;
|
||||
}
|
||||
|
||||
.bk.alert-light a {
|
||||
color: #686868;
|
||||
}
|
||||
|
||||
.bk.alert-dark a {
|
||||
color: #040505;
|
||||
}
|
43
frontend/static/frontend/vendor/panel/card.css
vendored
Normal file
43
frontend/static/frontend/vendor/panel/card.css
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
.bk.card {
|
||||
border: 1px solid rgba(0,0,0,.125);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.bk.accordion {
|
||||
border: 1px solid rgba(0,0,0,.125);
|
||||
}
|
||||
.bk.card-header {
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
border-radius: 0.25rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.25rem 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
.bk.accordion-header {
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.25rem 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
p.bk.card-button {
|
||||
background-color: transparent;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
margin-left: -15px;
|
||||
}
|
||||
.bk.card-header-row {
|
||||
position: relative !important;
|
||||
}
|
||||
.bk.card-title {
|
||||
align-items: center;
|
||||
display: flex !important;
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
padding: 0.25em;
|
||||
position: relative !important;
|
||||
}
|
41
frontend/static/frontend/vendor/panel/dataframe.css
vendored
Normal file
41
frontend/static/frontend/vendor/panel/dataframe.css
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
table.panel-df {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border: none;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
color: black;
|
||||
font-size: 12px;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.panel-df tr, .panel-df th, .panel-df td {
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
padding: 0.5em 0.5em !important;
|
||||
line-height: normal;
|
||||
white-space: normal;
|
||||
max-width: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.panel-df tbody {
|
||||
display: table-row-group;
|
||||
vertical-align: middle;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.panel-df tbody tr:nth-child(odd) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.panel-df thead {
|
||||
border-bottom: 1px solid black;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.panel-df tr:hover {
|
||||
background: lightblue !important;
|
||||
cursor: pointer;
|
||||
}
|
194
frontend/static/frontend/vendor/panel/json.css
vendored
Normal file
194
frontend/static/frontend/vendor/panel/json.css
vendored
Normal file
|
@ -0,0 +1,194 @@
|
|||
.json-formatter-row {
|
||||
font-family: monospace;
|
||||
}
|
||||
.json-formatter-row,
|
||||
.json-formatter-row a,
|
||||
.json-formatter-row a:hover {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
.json-formatter-row .json-formatter-row {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.json-formatter-row .json-formatter-children.json-formatter-empty {
|
||||
opacity: 0.5;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.json-formatter-row .json-formatter-children.json-formatter-empty:after {
|
||||
display: none;
|
||||
}
|
||||
.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-object:after {
|
||||
content: "No properties";
|
||||
}
|
||||
.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-array:after {
|
||||
content: "[]";
|
||||
}
|
||||
.json-formatter-row .json-formatter-string,
|
||||
.json-formatter-row .json-formatter-stringifiable {
|
||||
color: green;
|
||||
white-space: pre;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.json-formatter-row .json-formatter-number {
|
||||
color: blue;
|
||||
}
|
||||
.json-formatter-row .json-formatter-boolean {
|
||||
color: red;
|
||||
}
|
||||
.json-formatter-row .json-formatter-null {
|
||||
color: #855A00;
|
||||
}
|
||||
.json-formatter-row .json-formatter-undefined {
|
||||
color: #ca0b69;
|
||||
}
|
||||
.json-formatter-row .json-formatter-function {
|
||||
color: #FF20ED;
|
||||
}
|
||||
.json-formatter-row .json-formatter-date {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.json-formatter-row .json-formatter-url {
|
||||
text-decoration: underline;
|
||||
color: blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
.json-formatter-row .json-formatter-bracket {
|
||||
color: blue;
|
||||
}
|
||||
.json-formatter-row .json-formatter-key {
|
||||
color: #00008B;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
.json-formatter-row .json-formatter-toggler-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
.json-formatter-row .json-formatter-toggler {
|
||||
line-height: 1.2rem;
|
||||
font-size: 0.7rem;
|
||||
vertical-align: middle;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
.json-formatter-row .json-formatter-toggler:after {
|
||||
display: inline-block;
|
||||
transition: transform 100ms ease-in;
|
||||
content: "\25BA";
|
||||
}
|
||||
.json-formatter-row > a > .json-formatter-preview-text {
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in;
|
||||
font-style: italic;
|
||||
}
|
||||
.json-formatter-row:hover > a > .json-formatter-preview-text {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.json-formatter-row.json-formatter-open > .json-formatter-toggler-link .json-formatter-toggler:after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.json-formatter-row.json-formatter-open > .json-formatter-children:after {
|
||||
display: inline-block;
|
||||
}
|
||||
.json-formatter-row.json-formatter-open > a > .json-formatter-preview-text {
|
||||
display: none;
|
||||
}
|
||||
.json-formatter-row.json-formatter-open.json-formatter-empty:after {
|
||||
display: block;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row {
|
||||
font-family: monospace;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row,
|
||||
.json-formatter-dark.json-formatter-row a,
|
||||
.json-formatter-dark.json-formatter-row a:hover {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-row {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty {
|
||||
opacity: 0.5;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty:after {
|
||||
display: none;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-object:after {
|
||||
content: "No properties";
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-array:after {
|
||||
content: "[]";
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-string,
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-stringifiable {
|
||||
color: #31F031;
|
||||
white-space: pre;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-number {
|
||||
color: #66C2FF;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-boolean {
|
||||
color: #EC4242;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-null {
|
||||
color: #EEC97D;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-undefined {
|
||||
color: #ef8fbe;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-function {
|
||||
color: #FD48CB;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-date {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-url {
|
||||
text-decoration: underline;
|
||||
color: #027BFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-bracket {
|
||||
color: #9494FF;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-key {
|
||||
color: #23A0DB;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-toggler-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-toggler {
|
||||
line-height: 1.2rem;
|
||||
font-size: 0.7rem;
|
||||
vertical-align: middle;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-toggler:after {
|
||||
display: inline-block;
|
||||
transition: transform 100ms ease-in;
|
||||
content: "\25BA";
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row > a > .json-formatter-preview-text {
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in;
|
||||
font-style: italic;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row:hover > a > .json-formatter-preview-text {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row.json-formatter-open > .json-formatter-toggler-link .json-formatter-toggler:after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row.json-formatter-open > .json-formatter-children:after {
|
||||
display: inline-block;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row.json-formatter-open > a > .json-formatter-preview-text {
|
||||
display: none;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row.json-formatter-open.json-formatter-empty:after {
|
||||
display: block;
|
||||
}
|
81
frontend/static/frontend/vendor/panel/markdown.css
vendored
Normal file
81
frontend/static/frontend/vendor/panel/markdown.css
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
.codehilite .hll { background-color: #ffffcc }
|
||||
.codehilite { background: #f8f8f8; }
|
||||
.codehilite .c { color: #408080; font-style: italic } /* Comment */
|
||||
.codehilite .err { border: 1px solid #FF0000 } /* Error */
|
||||
.codehilite .k { color: #008000; font-weight: bold } /* Keyword */
|
||||
.codehilite .o { color: #666666 } /* Operator */
|
||||
.codehilite .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
|
||||
.codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
|
||||
.codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
|
||||
.codehilite .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
|
||||
.codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
|
||||
.codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
|
||||
.codehilite .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.codehilite .ge { font-style: italic } /* Generic.Emph */
|
||||
.codehilite .gr { color: #FF0000 } /* Generic.Error */
|
||||
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.codehilite .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.codehilite .go { color: #888888 } /* Generic.Output */
|
||||
.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
|
||||
.codehilite .gs { font-weight: bold } /* Generic.Strong */
|
||||
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.codehilite .gt { color: #0044DD } /* Generic.Traceback */
|
||||
.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
|
||||
.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
|
||||
.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
|
||||
.codehilite .kp { color: #008000 } /* Keyword.Pseudo */
|
||||
.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
|
||||
.codehilite .kt { color: #B00040 } /* Keyword.Type */
|
||||
.codehilite .m { color: #666666 } /* Literal.Number */
|
||||
.codehilite .s { color: #BA2121 } /* Literal.String */
|
||||
.codehilite .na { color: #7D9029 } /* Name.Attribute */
|
||||
.codehilite .nb { color: #008000 } /* Name.Builtin */
|
||||
.codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
|
||||
.codehilite .no { color: #880000 } /* Name.Constant */
|
||||
.codehilite .nd { color: #AA22FF } /* Name.Decorator */
|
||||
.codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
|
||||
.codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
|
||||
.codehilite .nf { color: #0000FF } /* Name.Function */
|
||||
.codehilite .nl { color: #A0A000 } /* Name.Label */
|
||||
.codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
|
||||
.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
|
||||
.codehilite .nv { color: #19177C } /* Name.Variable */
|
||||
.codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
|
||||
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.codehilite .mb { color: #666666 } /* Literal.Number.Bin */
|
||||
.codehilite .mf { color: #666666 } /* Literal.Number.Float */
|
||||
.codehilite .mh { color: #666666 } /* Literal.Number.Hex */
|
||||
.codehilite .mi { color: #666666 } /* Literal.Number.Integer */
|
||||
.codehilite .mo { color: #666666 } /* Literal.Number.Oct */
|
||||
.codehilite .sa { color: #BA2121 } /* Literal.String.Affix */
|
||||
.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
|
||||
.codehilite .sc { color: #BA2121 } /* Literal.String.Char */
|
||||
.codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */
|
||||
.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
|
||||
.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
|
||||
.codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
|
||||
.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
|
||||
.codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
|
||||
.codehilite .sx { color: #008000 } /* Literal.String.Other */
|
||||
.codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
|
||||
.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
|
||||
.codehilite .ss { color: #19177C } /* Literal.String.Symbol */
|
||||
.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
|
||||
.codehilite .fm { color: #0000FF } /* Name.Function.Magic */
|
||||
.codehilite .vc { color: #19177C } /* Name.Variable.Class */
|
||||
.codehilite .vg { color: #19177C } /* Name.Variable.Global */
|
||||
.codehilite .vi { color: #19177C } /* Name.Variable.Instance */
|
||||
.codehilite .vm { color: #19177C } /* Name.Variable.Magic */
|
||||
.codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
|
||||
|
||||
.markdown h1 { margin-block-start: 0.34em }
|
||||
.markdown h2 { margin-block-start: 0.42em }
|
||||
.markdown h3 { margin-block-start: 0.5em }
|
||||
.markdown h4 { margin-block-start: 0.67em }
|
||||
.markdown h5 { margin-block-start: 0.84em }
|
||||
.markdown h6 { margin-block-start: 1.17em }
|
||||
.markdown ul { padding-inline-start: 2em }
|
||||
.markdown ol { padding-inline-start: 2em }
|
||||
.markdown strong { font-weight: 600 }
|
||||
.markdown a { color: -webkit-link }
|
||||
.markdown a { color: -moz-hyperlinkText }
|
288
frontend/static/frontend/vendor/panel/widgets.css
vendored
Normal file
288
frontend/static/frontend/vendor/panel/widgets.css
vendored
Normal file
|
@ -0,0 +1,288 @@
|
|||
.bk.panel-widget-box {
|
||||
min-height: 20px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #e3e3e3;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.05);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.05);
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
progress {
|
||||
appearance: none;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
|
||||
border: none;
|
||||
height: 20px;
|
||||
background-color: whiteSmoke;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 3px rgba(0,0,0,.5) inset;
|
||||
color: royalblue;
|
||||
position: relative;
|
||||
margin: 0 0 1.5em;
|
||||
}
|
||||
|
||||
progress[value]::-webkit-progress-bar {
|
||||
background-color: whiteSmoke;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 3px rgba(0,0,0,.5) inset;
|
||||
}
|
||||
|
||||
progress[value]::-webkit-progress-value {
|
||||
position: relative;
|
||||
|
||||
background-size: 35px 20px, 100% 100%, 100% 100%;
|
||||
border-radius:3px;
|
||||
}
|
||||
|
||||
progress.active:not([value])::before {
|
||||
background-position: 10%;
|
||||
animation-name: stripes;
|
||||
animation-duration: 3s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
progress[value]::-moz-progress-bar {
|
||||
background-size: 35px 20px, 100% 100%, 100% 100%;
|
||||
border-radius:3px;
|
||||
}
|
||||
|
||||
progress:not([value])::-moz-progress-bar {
|
||||
border-radius:3px;
|
||||
background:
|
||||
linear-gradient(-45deg, transparent 33%, rgba(0, 0, 0, 0.2) 33%, rgba(0, 0, 0, 0.2) 66%, transparent 66%) left/2.5em 1.5em;
|
||||
|
||||
}
|
||||
|
||||
progress.active:not([value])::-moz-progress-bar {
|
||||
background-position: 10%;
|
||||
animation-name: stripes;
|
||||
animation-duration: 3s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
progress.active:not([value])::-webkit-progress-bar {
|
||||
background-position: 10%;
|
||||
animation-name: stripes;
|
||||
animation-duration: 3s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
progress.primary[value]::-webkit-progress-value { background-color: #007bff; }
|
||||
progress.primary:not([value])::before { background-color: #007bff; }
|
||||
progress.primary:not([value])::-webkit-progress-bar { background-color: #007bff; }
|
||||
progress.primary::-moz-progress-bar { background-color: #007bff; }
|
||||
|
||||
progress.secondary[value]::-webkit-progress-value { background-color: #6c757d; }
|
||||
progress.secondary:not([value])::before { background-color: #6c757d; }
|
||||
progress.secondary:not([value])::-webkit-progress-bar { background-color: #6c757d; }
|
||||
progress.secondary::-moz-progress-bar { background-color: #6c757d; }
|
||||
|
||||
progress.success[value]::-webkit-progress-value { background-color: #28a745; }
|
||||
progress.success:not([value])::before { background-color: #28a745; }
|
||||
progress.success:not([value])::-webkit-progress-bar { background-color: #28a745; }
|
||||
progress.success::-moz-progress-bar { background-color: #28a745; }
|
||||
|
||||
progress.danger[value]::-webkit-progress-value { background-color: #dc3545; }
|
||||
progress.danger:not([value])::before { background-color: #dc3545; }
|
||||
progress.danger:not([value])::-webkit-progress-bar { background-color: #dc3545; }
|
||||
progress.danger::-moz-progress-bar { background-color: #dc3545; }
|
||||
|
||||
progress.warning[value]::-webkit-progress-value { background-color: #ffc107; }
|
||||
progress.warning:not([value])::before { background-color: #ffc107; }
|
||||
progress.warning:not([value])::-webkit-progress-bar { background-color: #ffc107; }
|
||||
progress.warning::-moz-progress-bar { background-color: #ffc107; }
|
||||
|
||||
progress.info[value]::-webkit-progress-value { background-color: #17a2b8; }
|
||||
progress.info:not([value])::before { background-color: #17a2b8; }
|
||||
progress.info:not([value])::-webkit-progress-bar { background-color: #17a2b8; }
|
||||
progress.info::-moz-progress-bar { background-color: #17a2b8; }
|
||||
|
||||
progress.light[value]::-webkit-progress-value { background-color: #f8f9fa; }
|
||||
progress.light:not([value])::before { background-color: #f8f9fa; }
|
||||
progress.light:not([value])::-webkit-progress-bar { background-color: #f8f9fa; }
|
||||
progress.light::-moz-progress-bar { background-color: #f8f9fa; }
|
||||
|
||||
progress.dark[value]::-webkit-progress-value { background-color: #343a40; }
|
||||
progress.dark:not([value])::-webkit-progress-bar { background-color: #343a40; }
|
||||
progress.dark:not([value])::before { background-color: #343a40; }
|
||||
progress.dark::-moz-progress-bar { background-color: #343a40; }
|
||||
|
||||
progress:not([value])::-webkit-progress-bar {
|
||||
border-radius: 3px;
|
||||
background:
|
||||
linear-gradient(-45deg, transparent 33%, rgba(0, 0, 0, 0.2) 33%, rgba(0, 0, 0, 0.2) 66%, transparent 66%) left/2.5em 1.5em;
|
||||
}
|
||||
progress:not([value])::before {
|
||||
content:" ";
|
||||
position:absolute;
|
||||
height: 20px;
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
border-radius: 3px;
|
||||
background:
|
||||
linear-gradient(-45deg, transparent 33%, rgba(0, 0, 0, 0.2) 33%, rgba(0, 0, 0, 0.2) 66%, transparent 66%) left/2.5em 1.5em;
|
||||
}
|
||||
|
||||
@keyframes stripes {
|
||||
from {background-position: 0%}
|
||||
to {background-position: 100%}
|
||||
}
|
||||
|
||||
.bk.loader::after {
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
-webkit-mask-image: radial-gradient(transparent 50%, rgba(0, 0, 0, 1) 54%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.bk-root .bk.loader.dark::after {
|
||||
background: #0f0f0f;
|
||||
}
|
||||
|
||||
.bk-root .bk.loader.light::after {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.bk-root .bk.loader.spin::after {
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.primary-light::after {
|
||||
background: linear-gradient(135deg, #f0f0f0 50%, transparent 50%), linear-gradient(45deg, #f0f0f0 50%, #007bff 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.secondary-light::after {
|
||||
background: linear-gradient(135deg, #f0f0f0 50%, transparent 50%), linear-gradient(45deg, #f0f0f0 50%, #6c757d 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.success-light::after {
|
||||
background: linear-gradient(135deg, #f0f0f0 50%, transparent 50%), linear-gradient(45deg, #f0f0f0 50%, #28a745 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.danger-light::after {
|
||||
background: linear-gradient(135deg, #f0f0f0 50%, transparent 50%), linear-gradient(45deg, #f0f0f0 50%, #dc3545 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.warning-light::after {
|
||||
background: linear-gradient(135deg, #f0f0f0 50%, transparent 50%), linear-gradient(45deg, #f0f0f0 50%, #ffc107 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.info-light::after {
|
||||
background: linear-gradient(135deg, #f0f0f0 50%, transparent 50%), linear-gradient(45deg, #f0f0f0 50%, #17a2b8 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.light-light::after {
|
||||
background: linear-gradient(135deg, #f0f0f0 50%, transparent 50%), linear-gradient(45deg, #f0f0f0 50%, #f8f9fa 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.dark-light::after {
|
||||
background: linear-gradient(135deg, #f0f0f0 50%, transparent 50%), linear-gradient(45deg, #f0f0f0 50%, #343a40 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.primary-dark::after {
|
||||
background: linear-gradient(135deg, #0f0f0f 50%, transparent 50%), linear-gradient(45deg, #0f0f0f 50%, #007bff 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.secondary-dark::after {
|
||||
background: linear-gradient(135deg, #0f0f0f 50%, transparent 50%), linear-gradient(45deg, #0f0f0f 50%, #6c757d 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.success-dark::after {
|
||||
background: linear-gradient(135deg, #0f0f0f 50%, transparent 50%), linear-gradient(45deg, #0f0f0f 50%, #28a745 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.danger-dark::after {
|
||||
background: linear-gradient(135deg, #0f0f0f 50%, transparent 50%), linear-gradient(45deg, #0f0f0f 50%, #dc3545 50%)
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.warning-dark::after {
|
||||
background: linear-gradient(135deg, #0f0f0f 50%, transparent 50%), linear-gradient(45deg, #0f0f0f 50%, #ffc107 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.info-dark::after {
|
||||
background: linear-gradient(135deg, #0f0f0f 50%, transparent 50%), linear-gradient(45deg, #0f0f0f 50%, #17a2b8 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.light-dark::after {
|
||||
background: linear-gradient(135deg, #0f0f0f 50%, transparent 50%), linear-gradient(45deg, #0f0f0f 50%, #f8f9fa 50%);
|
||||
}
|
||||
|
||||
.bk-root div.bk.loader.spin.dark-dark::after {
|
||||
background: linear-gradient(135deg, #0f0f0f 50%, transparent 50%), linear-gradient(45deg, #0f0f0f 50%, #343a40 50%);
|
||||
}
|
||||
|
||||
/* Safari */
|
||||
@-webkit-keyframes spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.dot div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 1px solid #000 !important;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dot-filled div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 1px solid #000 !important;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dot-filled.primary div {
|
||||
background-color: #007bff;
|
||||
}
|
||||
|
||||
.dot-filled.secondary div {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
|
||||
.dot-filled.success div {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.dot-filled.danger div {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
.dot-filled.warning div {
|
||||
background-color: #ffc107;
|
||||
}
|
||||
|
||||
.dot-filled.info div {
|
||||
background-color: #17a2b8;
|
||||
}
|
||||
|
||||
.dot-filled.dark div {
|
||||
background-color: #343a40;
|
||||
}
|
||||
|
||||
.dot-filled.light div {
|
||||
background-color: #f8f9fa;
|
||||
}
|
6
mood/static/mood/statistics_iframe.js
Normal file
6
mood/static/mood/statistics_iframe.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
var iframe = document.getElementById("plot");
|
||||
|
||||
iframe.onload = function(){
|
||||
iframe.style.height = iframe.contentWindow.document.body.scrollHeight + 'px';
|
||||
iframe.style.width = iframe.contentWindow.document.body.scrollWidth + 'px';
|
||||
}
|
53
mood/statistics.py
Normal file
53
mood/statistics.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
import holoviews as hv
|
||||
import pandas as pd
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from bokeh.models import HoverTool
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from .models import Status
|
||||
|
||||
def moodstats(mindate=None, maxdate=None, days=7):
|
||||
hv.extension('bokeh')
|
||||
|
||||
maxdate = maxdate or timezone.now()
|
||||
mindate = mindate or (maxdate - relativedelta(days=days))
|
||||
|
||||
tooltips = [
|
||||
('Date', '@date{%F %H:%M}'),
|
||||
('Value', '@value')
|
||||
]
|
||||
|
||||
formatters = {
|
||||
'@date': 'datetime'
|
||||
}
|
||||
|
||||
hover = HoverTool(tooltips=tooltips, formatters=formatters)
|
||||
|
||||
pointdict = {"date": [], "value": [], "color": []}
|
||||
|
||||
for status in Status.objects.filter(timestamp__gte=mindate, timestamp__lte=maxdate):
|
||||
if status.mood:
|
||||
pointdict["date"].append(status.timestamp)
|
||||
pointdict["value"].append(status.mood.value)
|
||||
pointdict["color"].append(status.mood.color)
|
||||
|
||||
pointframe = pd.DataFrame.from_dict(pointdict)
|
||||
|
||||
points = hv.Points(pointframe)
|
||||
|
||||
points.opts(
|
||||
tools=[hover], color='color', cmap='Category20',
|
||||
line_color='black', size=25,
|
||||
width=600, height=400, show_grid=True,
|
||||
title='Your Mood Entries')
|
||||
|
||||
pointtuples = [(pointdict["date"][i], pointdict["value"][i]) for i in range(len(pointdict["date"]))]
|
||||
|
||||
line = hv.Curve(pointtuples)
|
||||
|
||||
output = points * line
|
||||
output.opts(tools=["xwheel_zoom"])
|
||||
|
||||
return output
|
|
@ -1,4 +1,6 @@
|
|||
{% extends "frontend/base.html" %}
|
||||
{% load images %}
|
||||
{% load request %}
|
||||
{% block "content" %}
|
||||
|
||||
<div class="row">
|
||||
|
@ -13,7 +15,7 @@
|
|||
</div>
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div id="my_dataviz"></div>
|
||||
<iframe id="plot" src="plot/{% querystring %}" width="800px" height="450px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .views import StatusListView, StatusViewView, StatusDeleteView, StatusEditView, StatusCreateView, ActivityListView, ActivityEditView, ActivityCreateView, ActivityDeleteView, MoodListView, MoodEditView, NotificationCreateView, NotificationDeleteView, NotificationEditView, NotificationListView, MoodStatisticsView, MoodCSVView
|
||||
from .views import StatusListView, StatusViewView, StatusDeleteView, StatusEditView, StatusCreateView, ActivityListView, ActivityEditView, ActivityCreateView, ActivityDeleteView, MoodListView, MoodEditView, NotificationCreateView, NotificationDeleteView, NotificationEditView, NotificationListView, MoodStatisticsView, MoodCSVView, MoodPlotView
|
||||
|
||||
from django.urls import path, include
|
||||
|
||||
|
@ -22,4 +22,5 @@ urlpatterns = [
|
|||
path('notification/new/', NotificationCreateView.as_view(), name="notification_create"),
|
||||
path('statistics/', MoodStatisticsView.as_view(), name="statistics"),
|
||||
path('statistics/csv/', MoodCSVView.as_view(), name="statistics_csv"),
|
||||
path('statistics/plot/', MoodPlotView.as_view(), name="statistics_plot"),
|
||||
]
|
|
@ -4,15 +4,21 @@ from django.shortcuts import get_object_or_404
|
|||
from django.urls import reverse_lazy
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||
from django.utils.decorators import method_decorator
|
||||
|
||||
from .models import Status, Activity, Mood, StatusMedia, StatusActivity
|
||||
from .forms import StatusForm
|
||||
from .statistics import moodstats
|
||||
|
||||
from common.helpers import get_upload_path
|
||||
from common.templatetags.images import hvhtml
|
||||
from msgio.models import NotificationDailySchedule, Notification
|
||||
|
||||
from dateutil import relativedelta
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
class StatusListView(LoginRequiredMixin, ListView):
|
||||
template_name = "mood/status_list.html"
|
||||
model = Status
|
||||
|
@ -326,8 +332,6 @@ class MoodStatisticsView(LoginRequiredMixin, TemplateView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Statistics"
|
||||
context["scripts"] = ["frontend/vendor/d3/d3.min.js", "mood/statistics.js"]
|
||||
context["styles"] = ["mood/statistics.css"]
|
||||
return context
|
||||
|
||||
class MoodCSVView(LoginRequiredMixin, View):
|
||||
|
@ -335,14 +339,49 @@ class MoodCSVView(LoginRequiredMixin, View):
|
|||
res = HttpResponse(content_type="text/csv")
|
||||
res["content-disposition"] = 'filename="mood.csv"'
|
||||
|
||||
maxage = timezone.now()
|
||||
minage = maxage - relativedelta.relativedelta(weeks=1)
|
||||
startdate = request.GET.get("start")
|
||||
enddate = request.GET.get("end")
|
||||
|
||||
if enddate:
|
||||
maxdate = datetime.strptime(enddate, "%Y-%m-%d")
|
||||
else:
|
||||
maxdate = timezone.now()
|
||||
|
||||
if startdate:
|
||||
mindate = datetime.strptime(startdate, "%Y-%m-%d")
|
||||
else:
|
||||
mindate = maxdate - relativedelta.relativedelta(weeks=1)
|
||||
|
||||
output = "date,value"
|
||||
|
||||
for status in Status.objects.filter(user=request.user, timestamp__gte=minage, timestamp__lte=maxage):
|
||||
date = status.timestamp.strftime("%Y-%m-%d %H:%M")
|
||||
output += f"\n{date},{status.mood.value}"
|
||||
for status in Status.objects.filter(user=request.user, timestamp__gte=mindate, timestamp__lte=maxdate):
|
||||
if status.mood:
|
||||
date = status.timestamp.strftime("%Y-%m-%d %H:%M")
|
||||
output += f"\n{date},{status.mood.value}"
|
||||
|
||||
res.write(output)
|
||||
return res
|
||||
|
||||
class MoodPlotView(LoginRequiredMixin, View):
|
||||
@method_decorator(xframe_options_sameorigin)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
res = HttpResponse(content_type="text/html")
|
||||
|
||||
startdate = request.GET.get("start")
|
||||
enddate = request.GET.get("end")
|
||||
|
||||
if enddate:
|
||||
maxdate = datetime.strptime(enddate, "%Y-%m-%d")
|
||||
else:
|
||||
maxdate = timezone.now()
|
||||
|
||||
if startdate:
|
||||
mindate = datetime.strptime(startdate, "%Y-%m-%d")
|
||||
else:
|
||||
mindate = maxdate - relativedelta.relativedelta(weeks=1)
|
||||
|
||||
res.write(hvhtml(moodstats(mindate, maxdate)))
|
||||
return res
|
|
@ -15,3 +15,6 @@ argon2_cffi
|
|||
python-telegram-bot
|
||||
python-dateutil
|
||||
matrix-nio
|
||||
holoviews
|
||||
bokeh==2.3.0dev13
|
||||
panel==0.11.0a16
|
Loading…
Reference in a new issue