Commit 9390ec898ba718e20b3221e3adc62ec24478a386
1 parent
706febb3
Exists in
master
and in
3 other branches
Initial experimental time series.
Showing
6 changed files
with
463 additions
and
0 deletions
Show diff stats
... | ... | @@ -0,0 +1,37 @@ |
1 | +# WARNING : IF YOU BREAK THIS FILE YOU'LL BREAK THE WEBSITE. Tread carefully. | |
2 | +# Remember, you can test the website locally, see the README. | |
3 | + | |
4 | + | |
5 | +# The HTML metadata in the page header. | |
6 | +# Don't put double quotes (") in any of these fields. | |
7 | +meta: | |
8 | + title: Space Weather | |
9 | + keywords: | |
10 | + - science | |
11 | + - cdpp | |
12 | + - europlanet | |
13 | + - space | |
14 | + - weather | |
15 | + # Will be shown by search engines below the title of the page. | |
16 | + description: Space weather predictions around solar celestial bodies. | |
17 | + | |
18 | + | |
19 | +# A list of authors that will appear in the HTML metadata and possibly in the | |
20 | +# authorship page as well. The order does not matter, it will be shuffled. | |
21 | +authors: | |
22 | + - name: Antoine Goutenoir | |
23 | + role: Software Ninja | |
24 | + mail: antoine.goutenoir@irap.omp.eu | |
25 | + - name: Myriam Bouchemit | |
26 | + role: Server Maintenance | |
27 | + mail: myriam.bouchemit@irap.omp.eu | |
28 | + - name: Mikel Indurain | |
29 | + role: Simulation Software | |
30 | + mail: mindurain@irap.omp.eu | |
31 | + - name: Nicolas André | |
32 | + role: Project Lead | |
33 | + mail: nicolas.andre@irap.omp.eu | |
34 | + - name: Vincent Genot | |
35 | + role: Project Coordinator | |
36 | + mail: vincent.genot@irap.omp.eu | |
37 | + | ... | ... |
No preview for this file type
... | ... | @@ -0,0 +1,202 @@ |
1 | +import random | |
2 | +import datetime | |
3 | +import StringIO | |
4 | + | |
5 | +from os import listdir, environ | |
6 | +from os.path import isfile, join, abspath, dirname | |
7 | + | |
8 | +import csv | |
9 | +from pprint import pprint | |
10 | +from csv import writer as csv_writer | |
11 | +from yaml import load as yaml_load | |
12 | +from flask import Flask | |
13 | +from flask import redirect, url_for, send_from_directory | |
14 | +from flask import request | |
15 | +from jinja2 import Environment, FileSystemLoader | |
16 | +from netCDF4 import Dataset | |
17 | + | |
18 | +# from model.Config import Config | |
19 | +# from model.News import NewsCollection, News | |
20 | + | |
21 | + | |
22 | +# PATH RELATIVITY ############################################################# | |
23 | + | |
24 | +THIS_DIRECTORY = dirname(abspath(__file__)) | |
25 | + | |
26 | + | |
27 | +def get_path(relative_path): | |
28 | + return abspath(join(THIS_DIRECTORY, relative_path)) | |
29 | + | |
30 | + | |
31 | +# COLLECT GLOBAL INFORMATION FROM SOURCES ##################################### | |
32 | + | |
33 | +# VERSION | |
34 | +with open(get_path('../VERSION'), 'r') as version_file: | |
35 | + version = version_file.read().strip() | |
36 | + | |
37 | +# CONFIG | |
38 | +with open(get_path('../config.yml'), 'r') as config_file: | |
39 | + config = yaml_load(config_file.read()) | |
40 | + | |
41 | + | |
42 | +# SETUP FLASK ENGINE ########################################################## | |
43 | + | |
44 | +app = Flask(__name__, root_path=THIS_DIRECTORY) | |
45 | +app.debug = environ.get('DEBUG') == 'true' | |
46 | + | |
47 | + | |
48 | +# SETUP JINJA2 TEMPLATE ENGINE ################################################ | |
49 | + | |
50 | +def static_global(filename): | |
51 | + return url_for('static', filename=filename) | |
52 | + | |
53 | + | |
54 | +def shuffle_filter(seq): | |
55 | + """ | |
56 | + This shuffles the sequence it is applied to. | |
57 | + 'tis a failure of jinja2 to not provide a shuffle filter by default. | |
58 | + """ | |
59 | + try: | |
60 | + result = list(seq) | |
61 | + random.shuffle(result) | |
62 | + return result | |
63 | + except: | |
64 | + return seq | |
65 | + | |
66 | + | |
67 | +def markdown_filter(value, nl2br=False, p=True): | |
68 | + """ | |
69 | + nl2br: set to True to replace line breaks with <br> tags | |
70 | + p: set to False to remove the enclosing <p></p> tags | |
71 | + """ | |
72 | + from markdown import markdown | |
73 | + from markdown.extensions.nl2br import Nl2BrExtension | |
74 | + from markdown.extensions.abbr import AbbrExtension | |
75 | + extensions = [AbbrExtension()] | |
76 | + if nl2br is True: | |
77 | + extensions.append(Nl2BrExtension()) | |
78 | + markdowned = markdown(value, output_format='html5', extensions=extensions) | |
79 | + if p is False: | |
80 | + markdowned = markdowned.replace(r"<p>", "").replace(r"</p>", "") | |
81 | + return markdowned | |
82 | + | |
83 | + | |
84 | +tpl_engine = Environment(loader=FileSystemLoader([get_path('view')]), | |
85 | + trim_blocks=True, | |
86 | + lstrip_blocks=True) | |
87 | + | |
88 | +tpl_engine.globals.update( | |
89 | + url_for=url_for, | |
90 | + static=static_global, | |
91 | +) | |
92 | + | |
93 | +tpl_engine.filters['markdown'] = markdown_filter | |
94 | +tpl_engine.filters['md'] = markdown_filter | |
95 | +tpl_engine.filters['shuffle'] = shuffle_filter | |
96 | + | |
97 | +tpl_global_vars = { | |
98 | + 'request': request, | |
99 | + 'version': version, | |
100 | + 'config': config, | |
101 | + 'now': datetime.datetime.now(), | |
102 | +} | |
103 | + | |
104 | + | |
105 | +# HELPERS ##################################################################### | |
106 | + | |
107 | +def render_view(view, context=None): | |
108 | + """ | |
109 | + A simple helper to render [view] template with [context] vars. | |
110 | + It automatically adds the global template vars defined above, too. | |
111 | + It returns a string, usually the HTML contents to display. | |
112 | + """ | |
113 | + context = {} if context is None else context | |
114 | + return tpl_engine.get_template(view).render( | |
115 | + dict(tpl_global_vars.items() + context.items()) | |
116 | + ) | |
117 | + | |
118 | + | |
119 | +# def render_page(page, title="My Page", context=None): | |
120 | +# """ | |
121 | +# A simple helper to render the md_page.html template with [context] vars & | |
122 | +# the additional contents of `page/[page].md` in the `md_page` variable. | |
123 | +# It automagically adds the global template vars defined above, too. | |
124 | +# It returns a string, usually the HTML contents to display. | |
125 | +# """ | |
126 | +# if context is None: | |
127 | +# context = {} | |
128 | +# context['title'] = title | |
129 | +# context['md_page'] = '' | |
130 | +# with file(get_path('page/%s.md' % page)) as f: | |
131 | +# context['md_page'] = f.read() | |
132 | +# return tpl_engine.get_template('md_page.html').render( | |
133 | +# dict(tpl_global_vars.items() + context.items()) | |
134 | +# ) | |
135 | + | |
136 | + | |
137 | +# ROUTING ##################################################################### | |
138 | + | |
139 | +@app.route("/") | |
140 | +@app.route("/home.html") | |
141 | +@app.route("/index.html") | |
142 | +def home(): | |
143 | + return render_view('home.html.jinja2', {}) | |
144 | + | |
145 | + | |
146 | +@app.route("/inspect") | |
147 | +def analyze_cdf(): | |
148 | + cdf_to_inspect = get_path("../res/dummy.nc") | |
149 | + | |
150 | + si = StringIO.StringIO() | |
151 | + cw = csv.DictWriter(si, fieldnames=['Name', 'Shape', 'Length']) | |
152 | + cw.writeheader() | |
153 | + | |
154 | + # Time, StartTime, StopTime, V, B, N, T, Delta_angle, P_dyn, QualityFlag | |
155 | + cdf_handle = Dataset(cdf_to_inspect, "r", format="NETCDF4") | |
156 | + for variable in cdf_handle.variables: | |
157 | + v = cdf_handle.variables[variable] | |
158 | + cw.writerow({ | |
159 | + 'Name': variable, | |
160 | + 'Shape': v.shape, | |
161 | + 'Length': v.size, | |
162 | + }) | |
163 | + cdf_handle.close() | |
164 | + | |
165 | + return si.getvalue() | |
166 | + | |
167 | + | |
168 | +@app.route("/test.csv") | |
169 | +def get_csv(): | |
170 | + si = StringIO.StringIO() | |
171 | + cw = csv_writer(si) | |
172 | + | |
173 | + # Time, StartTime, StopTime, V, B, N, T, Delta_angle, P_dyn, QualityFlag | |
174 | + cdf_handle = Dataset(get_path("../res/dummy.nc"), "r", format="NETCDF4") | |
175 | + # YYYY DOY HH MM SS .ms | |
176 | + times = cdf_handle.variables['Time'] | |
177 | + data_v = cdf_handle.variables['V'] | |
178 | + data_b = cdf_handle.variables['B'] | |
179 | + data_p = cdf_handle.variables['P_dyn'] | |
180 | + cw.writerow(('time', 'vrad', 'vtan', 'magn', 'pdyn')) | |
181 | + for time, datum_v, datum_b, datum_p in zip(times, data_v, data_b, data_p): | |
182 | + # Day Of Year starts at 0, but for our datetime parser it starts at 1 | |
183 | + doy = '{:03d}'.format(int(''.join(time[4:7])) + 1) | |
184 | + d = datetime.datetime.strptime( | |
185 | + "%s%s%s" % (''.join(time[0:4]), doy, ''.join(time[7:])), | |
186 | + "%Y%j%H%M%S%f" | |
187 | + ) | |
188 | + cw.writerow(( | |
189 | + d.strftime("%Y-%m-%dT%H:%M:%S+00:00"), | |
190 | + datum_v[0], datum_v[1], | |
191 | + datum_b, datum_p | |
192 | + )) | |
193 | + cdf_handle.close() | |
194 | + | |
195 | + return si.getvalue() | |
196 | + | |
197 | +# MAIN ######################################################################## | |
198 | + | |
199 | +if __name__ == "__main__": | |
200 | + # Debug mode on, as the production server does not use this. | |
201 | + extra_files = [get_path('../config.yml')] | |
202 | + app.run(debug=True, extra_files=extra_files) | ... | ... |
... | ... | @@ -0,0 +1,136 @@ |
1 | +{% extends 'layout.html.jinja2' %} | |
2 | +{% set menu_section = 'home' %} | |
3 | +{% block title %}Home{% endblock %} | |
4 | + | |
5 | +{% block content %} | |
6 | + | |
7 | +<h2>Overview</h2> | |
8 | + | |
9 | + | |
10 | + | |
11 | +<p> | |
12 | +TODO | |
13 | +</p> | |
14 | + | |
15 | +<section id="time_series"></section> | |
16 | + | |
17 | +{% endblock %} | |
18 | + | |
19 | + | |
20 | +{% block styles %} | |
21 | + <style> | |
22 | + path.line { | |
23 | + fill: none; | |
24 | + stroke: steelblue; | |
25 | + stroke-width: 1px; | |
26 | + } | |
27 | + </style> | |
28 | +{% endblock %} | |
29 | + | |
30 | + | |
31 | +{% block scripts_footer %} | |
32 | +<script type="application/javascript" src="{{ static('js/d3.min.js') }}"></script> | |
33 | +<script type="application/javascript"> | |
34 | + | |
35 | +var swApp = (function (window, d3, $) { | |
36 | + | |
37 | + var $time_series = $("#time_series"); | |
38 | + | |
39 | + // fixme | |
40 | + var plotTimeSeries = function(data, options) { | |
41 | + console.log("Init time series with data", data, options); | |
42 | + | |
43 | + var xScale, yScale, xAxis, yAxis; | |
44 | + var line; | |
45 | + var svg, plotWrapper, path; | |
46 | + var width, height; | |
47 | + var margin = { | |
48 | + top: 30, | |
49 | + right: 20, | |
50 | + bottom: 30, | |
51 | + left: 60 | |
52 | + }; | |
53 | + | |
54 | + // INIT SCALES | |
55 | + xScale = d3.scaleTime().domain(d3.extent(data, function (d) { | |
56 | + return d.x; | |
57 | + })); | |
58 | + yScale = d3.scaleLinear().domain(d3.extent(data, function (d) { | |
59 | + return d.y; | |
60 | + })); | |
61 | + | |
62 | + // INIT AXISES | |
63 | + xAxis = d3.axisBottom() | |
64 | + .ticks(7, ",f") | |
65 | + .tickFormat(d3.timeFormat("%Y-%m-%d")); | |
66 | + yAxis = d3.axisLeft() | |
67 | + .ticks(15); | |
68 | + | |
69 | + // INIT LINE | |
70 | + line = d3.line() | |
71 | + .x(function (d) { return xScale(d.x) }) | |
72 | + .y(function (d) { return yScale(d.y) }); | |
73 | + | |
74 | + // INIT SVG | |
75 | + svg = d3.select('#time_series').append('svg'); | |
76 | + | |
77 | + plotWrapper = svg.append('g'); | |
78 | + | |
79 | + path = plotWrapper.append('path') | |
80 | + .datum(data) | |
81 | + .classed('line', true); | |
82 | + | |
83 | + plotWrapper.append('g') | |
84 | + .classed('x axis', true); | |
85 | + plotWrapper.append('g') | |
86 | + .classed('y axis', true); | |
87 | + | |
88 | + width = $time_series.width() - margin.left - margin.right; | |
89 | + height = .618 * width; | |
90 | + | |
91 | + xScale.range([0, width]); | |
92 | + yScale.range([height, 0]); | |
93 | + | |
94 | + svg.attr('width', width + margin.right + margin.left) | |
95 | + .attr('height', height + margin.top + margin.bottom); | |
96 | + | |
97 | + plotWrapper.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
98 | + | |
99 | + path.attr('d', line); | |
100 | + | |
101 | + xAxis.scale(xScale); | |
102 | + yAxis.scale(yScale); | |
103 | + | |
104 | + // if (window.innerWidth < 800) { xAxis.ticks(3); } | |
105 | + | |
106 | + svg.select('.x.axis') | |
107 | + .attr('transform', 'translate(0,' + height + ')') | |
108 | + .call(xAxis); | |
109 | + | |
110 | + svg.select('.y.axis') | |
111 | + .call(yAxis); | |
112 | + | |
113 | + | |
114 | + }; | |
115 | + | |
116 | + return { | |
117 | + plotTimeSeries: plotTimeSeries | |
118 | + }; | |
119 | +})(window, d3, jQuery); | |
120 | + | |
121 | + | |
122 | +jQuery().ready(function($){ | |
123 | + d3.csv("{{ url_for('get_csv') }}", function(csv){ | |
124 | + var timeFormat = d3.timeParse('%Y-%m-%dT%H:%M:%S%Z'); | |
125 | + var data = {'pdyn': [], 'magn': []}; | |
126 | + csv.forEach(function (d) { | |
127 | + data['pdyn'].push({x: timeFormat(d['time']), y: parseFloat(d['pdyn'])}); | |
128 | + data['magn'].push({x: timeFormat(d['time']), y: parseFloat(d['magn'])}); | |
129 | + }); | |
130 | + swApp.plotTimeSeries(data['pdyn'], {}); | |
131 | + swApp.plotTimeSeries(data['magn'], {}); | |
132 | + }); | |
133 | +}); | |
134 | +</script> | |
135 | + | |
136 | +{% endblock %} | ... | ... |
... | ... | @@ -0,0 +1,84 @@ |
1 | +<!DOCTYPE HTML> | |
2 | +<html> | |
3 | + <head> | |
4 | + <meta charset="utf-8"> | |
5 | + | |
6 | + <title>{% block title %}Welcome{% endblock %} — {{ config.meta.title }}</title> | |
7 | + | |
8 | + <meta name="description" content="{{ config.meta.description }}"> | |
9 | + <meta name="keywords" content="{{ config.meta.keywords | join(', ') }}"> | |
10 | +{% for author in config.authors | shuffle %} | |
11 | + <meta name="author" content="{{ author.name }} <{{ author.mail }}>"> | |
12 | +{% endfor %} | |
13 | + | |
14 | + <meta name="viewport" content="width=device-width, initial-scale=1"> | |
15 | + | |
16 | + <link rel="shortcut icon" href="{{ static('img/favicon.ico') }}"> | |
17 | + | |
18 | + <link rel="stylesheet" type="text/css" href="{{ static('css/main.css') }}"> | |
19 | +{% block styles %}{% endblock %} | |
20 | + | |
21 | +{# <script type="text/javascript">#} | |
22 | +{# // Good javascript is no javascript -- hurray! \o/#} | |
23 | +{# </script>#} | |
24 | +{% block scripts_header %}{% endblock %} | |
25 | + </head> | |
26 | + | |
27 | + <body> | |
28 | + | |
29 | + <div id="page-container" class="container"> | |
30 | + | |
31 | + <header> | |
32 | + <div class="header"> | |
33 | + <img class="logo" src="{{ static('img/logo/logo_cnrs.png') }}" alt="CNRS" id="logo-cnrs" /> | |
34 | + <h1 class="hidden">Space Weather <small>v{{ version }}</small></h1> | |
35 | + <span id="version-number">v{{ version }}</span> | |
36 | + </div> | |
37 | + </header> | |
38 | + | |
39 | + <div class=""> | |
40 | + | |
41 | + <nav id="menu" role="navigation"> | |
42 | + <ul> | |
43 | + <li class="home{% if menu_section == 'home' %} active{% endif %}"> | |
44 | + <a href="index.html"></a> | |
45 | + </li> | |
46 | + <li{% if menu_section == 'authors' %} class="active"{% endif %}> | |
47 | + <a href="authors.html">Authors</a> | |
48 | + </li> | |
49 | + </ul> | |
50 | + </nav> | |
51 | + | |
52 | + <div id="content"> | |
53 | +{% block content %} | |
54 | + <p>Please override the "content" block in the page template.</p> | |
55 | +{% endblock %} | |
56 | + </div> | |
57 | + | |
58 | + </div> | |
59 | + | |
60 | + <footer> | |
61 | + <a href="http://www.irap.omp.eu/">IRAP</a> OMP © 2017 | |
62 | + </footer> | |
63 | + </div> | |
64 | + | |
65 | + <script type="application/javascript"> | |
66 | + // fixme: GOOGLE ANALYTICS | |
67 | + /* | |
68 | + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | |
69 | + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | |
70 | + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | |
71 | + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); | |
72 | + ga('create', 'UA-56037907-1', 'auto'); | |
73 | + ga('send', 'pageview'); | |
74 | + */ | |
75 | + </script> | |
76 | + | |
77 | + <script type="application/javascript" src="{{ static('js/jquery.min.js') }}"></script> | |
78 | + <script type="application/javascript"> | |
79 | + jQuery().ready(function($){ $(".nojs").hide(); }); | |
80 | + </script> | |
81 | +{% block scripts_footer %}{% endblock %} | |
82 | + | |
83 | + </body> | |
84 | +</html> | |
0 | 85 | \ No newline at end of file | ... | ... |