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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 | \ No newline at end of file | 85 | \ No newline at end of file |