Skip to content

Commit f6faaa7

Browse files
committed
Initial commit
1 parent a0886f9 commit f6faaa7

9 files changed

Lines changed: 2348 additions & 2 deletions

README.md

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,53 @@
1-
# survey_dashboard
2-
A dashboard build with bokeh to provide interative data visualization and exploration of survey results.
1+
# Survey dashboard
2+
3+
A dashboard to display survey data in an interactive way.
4+
5+
## Overview
6+
7+
A dashboard using bokeh sever, developed to display data from HMC surveys in an interactive explorer way.
8+
9+
## Installation
10+
11+
12+
## Usage
13+
14+
15+
* Navigate to `http://localhost:8000/` in your browser.
16+
17+
18+
## Development
19+
20+
To embed the dashboard into any website, first you have to host a bokeh server with this application somewhere and then you can embed it with bokehs `sever_document` function [see](https://docs.bokeh.org/en/latest/docs/user_guide/embed.html#app-documents)
21+
22+
Do steps under `usage` above, but for a public exposed URL.
23+
24+
Add the code from 'script' to you website:
25+
26+
```python
27+
from bokeh.embed import server_document
28+
script = server_document("url_to_running_server")
29+
script
30+
```
31+
32+
## Copyright and Licence
33+
34+
See [LICENSE](./LICENSE).
35+
36+
### Main used libraries and dependencies
37+
38+
The following libraries are used directly (i.e. not only transitively) in this project:
39+
40+
41+
## Acknowledgements
42+
43+
<div>
44+
<img style="vertical-align: middle;" alt="HMC Logo" src="https://github.com/Materials-Data-Science-and-Informatics/Logos/raw/main/HMC/HMC_Logo_M.png" width=50% height=50% />
45+
&nbsp;&nbsp;
46+
<img style="vertical-align: middle;" alt="FZJ Logo" src="https://github.com/Materials-Data-Science-and-Informatics/Logos/raw/main/FZJ/FZJ.png" width=30% height=30% />
47+
</div>
48+
<br />
49+
50+
This project was developed at the Institute for Materials Data Science and Informatics
51+
(IAS-9) of the Jülich Research Center and funded by the Helmholtz Metadata Collaboration
52+
(HMC), an incubator-platform of the Helmholtz Association within the framework of the
53+
Information and Data Science strategic initiative.

dashboard/__init__.py

Whitespace-only changes.

dashboard/data/HMC_community_survey2021_merged_all_replies.csv

Lines changed: 1284 additions & 0 deletions
Large diffs are not rendered by default.

dashboard/data/filters.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
All
2+
AST
3+
Earth & Environment
4+
Energy
5+
Health
6+
Information
7+
Matter

dashboard/data/questions.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Question 1
2+
Question 2
3+
Question 3, this is longer
4+
Question 4, this is longer still, Why did I do this?
5+
Question 5, this is way to long and should probly be shortened, or it is not displayed well I guess. But anyway it should be confirmed anyhow. Thanks!

dashboard/description.html

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
<style>
3+
h1 {
4+
margin: 1em 0 0 0;
5+
color: #2e484c;
6+
font-family: 'Julius Sans One', sans-serif;
7+
font-size: 1.8em;
8+
text-transform: uppercase;
9+
}
10+
a:link {
11+
font-weight: bold;
12+
text-decoration: none;
13+
color: #0d8ba1;
14+
}
15+
a:visited {
16+
font-weight: bold;
17+
text-decoration: none;
18+
color: #1a5952;
19+
}
20+
a:hover, a:focus, a:active {
21+
text-decoration: underline;
22+
color: #9685BA;
23+
}
24+
p {
25+
font: "Libre Baskerville", sans-serif;
26+
text-align: justify;
27+
text-justify: inter-word;
28+
width: 80%;
29+
max-width: 800;
30+
}
31+
32+
</style>
33+
34+
<h1>The HMC Survey explorer</h1>
35+
36+
<p>
37+
Explorer interactively the HMC 2021 Survey results.
38+
Interact with the visualizations and widgets on the left to explorer the data.
39+
</p>
40+
<p>
41+
<br />

dashboard/main.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# myapp.py
2+
3+
import random
4+
5+
import os
6+
from random import randint
7+
from os.path import dirname, join
8+
9+
from bokeh.layouts import column, row
10+
from bokeh.models import Button
11+
from bokeh.palettes import RdYlBu3
12+
from bokeh.plotting import curdoc
13+
from bokeh.models import ColumnDataSource, Div, Select
14+
from bokeh.plotting import figure as bokeh_figure
15+
from bokeh.models import CheckboxButtonGroup, CustomJS
16+
from masci_tools.vis.bokeh_plots import bokeh_scatter, bokeh_multi_scatter
17+
from .plots import bokeh_barchart, bokeh_piechart
18+
19+
desc = Div(text=open(join(dirname(__file__), "description.html")).read(), sizing_mode="stretch_width")
20+
21+
pwd = os.getcwd()
22+
questions = open(join(pwd, 'dashboard/data/questions.txt')).read().split('\n')
23+
QUESTION_MAP = {question : i for i, question in enumerate(questions)}
24+
25+
question_select = Select(title="Question", value="Question 1",
26+
options=questions)
27+
filter_select = Select(title="Data Filter", value="All",
28+
options=open(join(pwd, 'dashboard/data/filters.txt')).read().split('\n'))
29+
30+
#LABELS = open(join(pwd, 'dashboard/data/filters.txt')).read().split('\n')
31+
32+
#filter_select = CheckboxButtonGroup(labels=LABELS, active=[0, 1])
33+
#filter_select.js_on_click(CustomJS(code="""
34+
# console.log('checkbox_button_group: active=' + this.active, this.toString())
35+
#"""))
36+
chart_select = Select(title="Chart type", value="bar",
37+
options=['bar', 'pie'])
38+
39+
# Controls 2
40+
question_select2 = Select(title="Question X-Axis", value="Question 1",
41+
options=questions)
42+
question_select3 = Select(title="Question Y-Axis", value="Question 2",
43+
options=questions)
44+
45+
TOOLTIPS=[
46+
("Title", "@title"),
47+
("Answer", "@x"),
48+
("Number of Answers", "@y")
49+
]
50+
51+
52+
x = [str(i) for i in range(7)]
53+
xs = [x,x,x,x,x]
54+
#xs = [[f'1 pli bla blub {i}' for i in x], [f'2 pli bla blub {i}' for i in x], [f'3 pli bla blub {i}' for i in x], [f'4 pli bla blub {i}' for i in x], [f'5 pli bla blub {i}' for i in x]]
55+
ys = [[randint(0, 1200) for i in x], [randint(0, 1200) for i in x], [randint(0, 1200) for i in x],[randint(0, 1200) for i in x], [randint(0, 1200) for i in x]]
56+
57+
58+
59+
#fig = bokeh_figure(height=600, width=700, title="", toolbar_location='right', tooltips=TOOLTIPS, sizing_mode="scale_both")
60+
source = ColumnDataSource(data=dict(value=xs[0], counts=ys[0]))
61+
source2 = ColumnDataSource(data=dict(value=xs[1], counts=ys[1]))
62+
63+
fig = bokeh_barchart(source, factors=xs[0])
64+
fig2 = bokeh_barchart(source2, factors=xs[1])
65+
66+
#fig2 = bokeh_multi_scatter(source2, marker_size=[int(x)+6 for x in xs[1])
67+
68+
69+
def select_data():
70+
"""Select the data to display"""
71+
question = question_select.value
72+
index = QUESTION_MAP[question]
73+
selected = ColumnDataSource(data=dict(value=xs[index], counts=ys[index], factors=xs[index]))#, title=question))
74+
return selected
75+
76+
def select_data2():
77+
"""Select the data to display"""
78+
question = question_select2.value
79+
question2 = question_select3.value
80+
index = QUESTION_MAP[question]
81+
selected = ColumnDataSource(data=dict(value=xs[index], counts=ys[index], factors=xs[index]))#, title=question))
82+
return selected
83+
84+
85+
86+
def select_chart():
87+
"""Select how the data should be displayed"""
88+
charttype = chart_select.value.strip()
89+
return charttype
90+
91+
def update():
92+
"""Update the charts"""
93+
df = select_data()
94+
charttype = select_chart()
95+
96+
source.data = dict(value=df.data['value'], counts=df.data['counts'])
97+
98+
if charttype == 'pie':
99+
fig = bokeh_piechart(source)#, figure=figure)
100+
row1.children[1] = fig
101+
else:
102+
fig = bokeh_barchart(source, factors=df.data['factors'])#, figure=figure)
103+
row1.children[1] = fig
104+
#show(fig)
105+
106+
def update2():
107+
"""Update the charts"""
108+
df = select_data2()
109+
source2.data = dict(value=df.data['value'], counts=df.data['counts'])
110+
fig2 = bokeh_barchart(source2, factors=df.data['factors'])
111+
112+
controls = [question_select, filter_select, chart_select]
113+
114+
inputs = column(*controls, width=200)
115+
row1 = row(inputs, fig)
116+
for control in controls:
117+
control.on_change('value', lambda attr, old, new: update())
118+
first = column(desc, row1 , sizing_mode="scale_both")
119+
120+
121+
controls2 = [question_select2, question_select3]
122+
for control in controls2:
123+
control.on_change('value', lambda attr, old, new: update2())
124+
inputs2 = column(*controls2, width=200)
125+
layout = column(first, row(inputs2, fig2), sizing_mode="scale_both")
126+
127+
curdoc().add_root(layout)
128+
curdoc().title = "HMC Survey Dashboard"

dashboard/plots.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
2+
import numpy as np
3+
from bokeh.models import ColumnDataSource
4+
from bokeh.plotting import figure as bokeh_figure
5+
from bokeh.palettes import Category20c
6+
from bokeh.transform import factor_cmap
7+
from bokeh.transform import cumsum
8+
from bokeh.models import FactorRange
9+
10+
11+
# for tests
12+
#import pandas as pd
13+
#import random
14+
#from bokeh.io import output_notebook, show, push_notebook
15+
16+
#x = [str(i) for i in range(7)]
17+
#xs = [x,x,x,x,x]
18+
#xlabels = [[f'1 pli bla blub {i}' for i in x], [f'2 pli bla blub {i}' for i in x], [f'3 pli bla blub {i}' for i in x], [f'4 pli bla blub {i}' for i in x], [f'5 pli bla blub {i}' for i in x]]
19+
#ys = [[random.randint(0, 1200) for i in x], [random.randint(0, 1200) for i in x], [random.randint(0, 1200) for i in x],[random.randint(0, 1200) for i in x], [random.randint(0, 1200) for i in x]]
20+
21+
# bokeh bar plot
22+
def bokeh_barchart(df, x='value', y='counts', factors=None, figure=None, title='', width=0.9, xlabel='Answers', ylabel='Number of answers', palette=Category20c, fill_color='color'):
23+
"""Plot an interactive bar chart with bokeh"""
24+
25+
if isinstance(df, ColumnDataSource):
26+
xdata = df.data[x]
27+
if not 'color' in df.column_names:
28+
if not len(xdata) > 20:
29+
df.data['color'] = Category20c[len(xdata)] # ! if len(xdata)>20 this fails
30+
else:
31+
xdata = df[x]
32+
if not 'color' in df.columns:
33+
df['color'] = Category20c[len(xdata)] # ! if len(xdata)>20 this fails
34+
35+
if figure is None:
36+
if factors is not None:
37+
fig = bokeh_figure(plot_height=600, plot_width=600,
38+
title=title,
39+
toolbar_location='right',
40+
x_range=FactorRange(factors=factors),
41+
tools='',#'hover',
42+
tooltips=[('Data', f'@{x}'), ('Count', f'@{y}')])
43+
else:
44+
fig = bokeh_figure(plot_height=600, plot_width=600,
45+
title=title,
46+
toolbar_location='right',
47+
tools='',#'hover',
48+
tooltips=[('Data', f'@{x}'), ('Count', f'@{y}')])
49+
else:
50+
fig = figure
51+
#if factors is not None:
52+
# fig.x_range=FactorRange(factors=factors)
53+
54+
fig.vbar(x=x, top=y, width=width, source=df, line_color="white", fill_color=fill_color)#factor_cmap('x', palette=palette, factors=factors, start=1, end=2))
55+
fig.y_range.start = 0
56+
fig.x_range.range_padding = 0.1
57+
fig.xaxis.major_label_orientation = 1
58+
fig.xgrid.grid_line_color = None
59+
fig.yaxis.axis_label = ylabel
60+
fig.xaxis.axis_label = xlabel
61+
62+
return fig
63+
64+
65+
# test
66+
#df_test = pd.DataFrame(data=dict(value=xs[0], counts=ys[0]))
67+
#fig = bokeh_barchart(df_test, factors=xs[0])
68+
#show(fig)
69+
70+
# test2
71+
#counts2 = sum(zip(*ys), ())
72+
#factors = [(str(ques), ans) for ques in range(len(xs)) for ans in xs[0]] # factors have to be unique, and strings
73+
#df_test3 = ColumnDataSource(data=dict(value=factors, counts=counts2))
74+
#palette = Category20c[len(ys[0])]
75+
#fig = bokeh_barchart(df_test3, factors=factors, fill_color=factor_cmap('value', palette=palette, factors=xs[0], start=1, end=2))
76+
#show(fig)
77+
78+
79+
# bokeh piechart
80+
def bokeh_piechart(df, x='value', y='counts', figure=None, radius=0.8, title=''):
81+
"""Draw an interactive piechart with bokeh"""
82+
83+
from math import pi
84+
85+
if isinstance(df, ColumnDataSource):
86+
ydata = df.data[y]
87+
if not 'color' in df.column_names:
88+
if not len(ydata) > 20:
89+
df.data['color'] = Category20c[len(ydata)] # ! if len(xdata)>20 this fails
90+
else:
91+
ydata = df[y]
92+
if not 'color' in df.columns:
93+
df['color'] = Category20c[len(ydata)] # ! if len(xdata)>20 this fails
94+
95+
ydata = np.array(ydata)
96+
df.data['percent'] = ydata / sum(ydata)
97+
df.data['angle'] = ydata / sum(list(ydata)) * 2 * pi
98+
99+
if figure is None:
100+
fig = bokeh_figure(plot_height=600, plot_width=600,
101+
title=title,
102+
toolbar_location='right',
103+
tools='',#hover',
104+
tooltips=[('Data', f'@{x}'),
105+
('Percent', '@percent{0.00%}'),
106+
('Count', f'@{y}')])
107+
else:
108+
fig = figure
109+
#fig.add_layout(Legend(), 'right')
110+
fig.wedge(x=0,
111+
y=1,
112+
radius=radius,
113+
start_angle=cumsum('angle', include_zero=True),
114+
end_angle=cumsum('angle'),
115+
line_color='white',
116+
fill_color='color',
117+
legend_field='xlabel',
118+
source=df)
119+
120+
fig.axis.axis_label = None
121+
fig.axis.visible = False
122+
fig.grid.grid_line_color = None
123+
return fig
124+
125+
# test
126+
#df_test = pd.DataFrame(data=dict(value=xs[0], counts=ys[0], xlabel=xs[0]))
127+
#fig = bokeh_piechart(df_test)
128+
#show(fig)

0 commit comments

Comments
 (0)