Commit 9390ec898ba718e20b3221e3adc62ec24478a386

Authored by Goutte
1 parent 706febb3

Initial experimental time series.

CHANGELOG.md 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +## 0.0.0
  2 +
  3 +- Initial website skeleton
  4 +- Initial data bridge between netCDF and CSV
0 5 \ No newline at end of file
... ...
config.yml 0 → 100644
... ... @@ -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 +
... ...
res/dummy.nc 0 → 100644
No preview for this file type
web/run.py 0 → 100755
... ... @@ -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)
... ...
web/view/home.html.jinja2 0 → 100755
... ... @@ -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 %}
... ...
web/view/layout.html.jinja2 0 → 100755
... ... @@ -0,0 +1,84 @@
  1 +<!DOCTYPE HTML>
  2 +<html>
  3 + <head>
  4 + <meta charset="utf-8">
  5 +
  6 + <title>{% block title %}Welcome{% endblock %} &mdash; {{ 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 &copy 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
... ...