Commits

Chris Mutel  committed f3700bb

0.6alpha. Everything just a bit shinier.

  • Participants
  • Parent commits 19061fb

Comments (0)

Files changed (12)

 Brightway2-UI
 =============
 
-This is a web and command line user interface for the the Brightway2 LCA software.
+This is a web and command line user interface, part of the `Brightway2 LCA framework <http://brightwaylca.org>`_. The source code is hosted on `Bitucket <https://bitbucket.org/cmutel/brightway2-ui>`_.

File bw2ui/bin/bw2-controller.py

 Usage:
   bw2-controller.py list [databases|methods]
   bw2-controller.py details <name>
+  bw2-controller.py copy <name> <newname>
+  bw2-controller.py backup <name>
+  bw2-controller.py validate <name>
+  bw2-controller.py versions <name>
+  bw2-controller.py revert <name> <revision>
   bw2-controller.py remove <name>
-  bw2-controller.py copy <name> <newname>
-  bw2-controller.py revert <name> <revision>
-  bw2-controller.py import [database|method] <path>
+  bw2-controller.py import <path> <name>
+  bw2-controller.py export <name> [--include-dependencies]
+  bw2-controller.py setup
 
 Options:
   -h --help     Show this screen.
 
 
 if __name__ == '__main__':
-    arguments = docopt(__doc__, version='Brightway2 CLI 0.1')
+    arguments = docopt(__doc__, version='Brightway2 CLI 1.0')
     terminal_format(Controller().dispatch(**arguments))

File bw2ui/bin/bw2-web.py

 from bw2ui.web import bw2webapp
 from bw2ui.utils import clean_jobs_directory
 from docopt import docopt
-from werkzeug.serving import run_simple
+# from werkzeug.serving import run_simple
 import threading
 import webbrowser
 
         "processes": args.get("<processes>", 0) or 3,
     }
 
-    if args["--debug"]:
-        bw2webapp.run(debug=False)
-    else:
-        run_simple(host, port, bw2webapp, use_evalex=True, **kwargs)
+    # if args["--debug"]:
+    # run_simple disabled because multiple workers cause cache conflicts...
+    bw2webapp.run(debug=False)
+    # else:
+    #     run_simple(host, port, bw2webapp, use_evalex=True, **kwargs)

File bw2ui/controller.py

 # -*- coding: utf-8 -*-
-from brightway2 import databases, methods, Database, Method, config, reset_meta
-from bw2data.io import EcospoldImporter, EcospoldImpactAssessmentImporter
-from errors import UnknownAction
+from brightway2 import databases, methods, Database, config
+from bw2data.io import EcospoldImporter, download_biosphere, \
+    BW2PackageImporter, BW2PackageExporter
+from errors import UnknownAction, UnknownDatabase
+import datetime
+
+
+def strfdelta(tdelta):
+    """From http://stackoverflow.com/questions/8906926/formatting-python-timedelta-objects"""
+    d = {"days": tdelta.days}
+    d["hours"], rem = divmod(tdelta.seconds, 3600)
+    d["minutes"], d["seconds"] = divmod(rem, 60)
+    fmt = "{days} days {hours}h:{minutes}m:{seconds}s old"
+    return fmt.format(**d)
 
 
 class Controller(object):
-    def database_or_method(self, name):
-        if name in databases:
-            return (name, "database")
-        elif name in methods:
-            return (name, "method")
-        elif tuple(name.split(":")) in methods:
-            return (tuple(name.split(":")), "method")
-        else:
-            raise ValueError
-
     def dispatch(self, **kwargs):
-        if kwargs.get('list', None):
-            return self.list(kwargs)
-        elif kwargs.get('details', None):
-            return self.details(kwargs)
-        elif kwargs.get('remove', None):
-            return self.remove(kwargs)
-        elif kwargs.get('import', None) and kwargs.get('database', None):
-            return self.import_database(kwargs)
-        elif kwargs.get('import', None) and kwargs.get('method', None):
-            return self.import_method(kwargs)
+        options = ("list", "details", "copy", "backup", "validate", "versions",
+            "revert", "remove", "export", "setup")
+        for option in options:
+            if kwargs[option]:
+                return getattr(self, option)(kwargs)
+        if kwargs["import"]:
+            return self.importer(kwargs)
         raise UnknownAction("No suitable action found")
 
+    def get_name(self, kwargs):
+        name = kwargs['<name>']
+        if name not in databases:
+            raise UnknownDatabase("Can't find the database %s" % name)
+        return name
+
     def list(self, kwargs):
-        if kwargs.get('databases', None):
+        if kwargs['databases']:
             return databases.list
         else:
             return methods.list
 
     def details(self, kwargs):
-        name, kind = self.database_or_method(kwargs.get('<name>', None))
-        if kind == "database":
-            return databases[name]
-        else:
-            return methods[name]
+        return databases[self.get_name(kwargs)]
+
+    def copy(self, kwargs):
+        name = self.get_name(kwargs)
+        new_name = kwargs['<newname>']
+        Database(name).copy(new_name)
+        return u"%s copy to %s successful" % (name, new_name)
+
+    def backup(self, kwargs):
+        name = self.get_name(kwargs)
+        Database(name).backup()
+        return u"%s backup successful" % name
+
+    def validate(self, kwargs):
+        name = self.get_name(kwargs)
+        db = Database(name)
+        db.validate(db.load())
+        return u"%s data validated successfully" % name
+
+    def versions(self, kwargs):
+        now = datetime.datetime.now()
+        return [(x[0], x[1].strftime("Created %A, %d. %B %Y %I:%M%p"),
+            strfdelta(now - x[1])) for x in Database(self.get_name(kwargs)
+            ).versions()]
+
+    def revert(self, kwargs):
+        name = self.get_name(kwargs)
+        revision = int(kwargs["<revision>"])
+        Database(name).revert(revision)
+        return u"%s reverted to revision %s" % (name, revision)
 
     def remove(self, kwargs):
-        name, kind = self.database_or_method(kwargs.get('<name>', None))
-        if kind == "database":
-            Database(name).deregister()
-        else:
-            Method(name).deregister()
+        name = self.get_name(kwargs)
+        Database(name).deregister()
+        return u"%s removed" % name
 
-    def import_database(self, path):
-        EcospoldImporter.import_directory(path)
+    def importer(self, kwargs):
+        return
+        # EcospoldImporter().importer(path, name)
 
-    def import_method(self, path):
-        EcospoldImpactAssessmentImporter(path)
+    def export(self, kwargs):
+        name = self.get_name(kwargs)
+        dependencies = kwargs["--include-dependencies"]
+        path = BW2PackageExporter().export(name, dependencies)
+        return u"%s exported to Brightway package: %s" % (name, path)
 
-    def setup_directory(self, path):
-        config.dir = path
+    def setup(self):
         config.create_basic_directories()
-        reset_meta()
+        download_biosphere()
+        return u"Brightway2 setup successful"

File bw2ui/errors.py

 class UnknownAction(StandardError):
     pass
+
+
+class UnknownDatabase(StandardError):
+    pass

File bw2ui/utils.py

-from pprint import pprint
+from pprint import pformat
 
 
 def terminal_format(data):
-    pprint(data)
+    print pformat(data).replace('u"', '"').replace("u'", "'")
 
 
 def clean_jobs_directory():

File bw2ui/web/app.py

 # -*- coding: utf-8 -*-
 from __future__ import division
 from brightway2 import config, databases, methods, Database, Method, \
-    JsonWrapper, reset_meta
-from bw2analyzer import ContributionAnalysis, DatabaseExplorer, \
-    SerializedLCAReport
-from bw2calc import LCA, ParallelMonteCarlo
+    JsonWrapper, set_data_dir, setup
+from bw2analyzer import DatabaseExplorer, SerializedLCAReport
 from bw2calc.speed_test import SpeedTest
 from bw2data.io import EcospoldImporter, EcospoldImpactAssessmentImporter
 from flask import Flask, url_for, render_template, request, redirect, abort
 from jobs import JobDispatch, InvalidJob
 from utils import get_job_id, get_job, set_job_status, json_response
 import itertools
-import math
 import multiprocessing
-import numpy as np
 import os
-import platform
-import requests
 import urllib2
 
 
 def set_path():
     path = urllib2.unquote(request.form["path"])
     dirname = urllib2.unquote(request.form["dirname"])
-    dirpath = os.path.join(path, dirname)
-    if not os.path.exists(dirpath):
-        os.mkdir(dirpath)
-
-    user_dir = os.path.expanduser("~")
-    if platform.system == "Windows":
-        filename = "brightway2path.txt"
-    else:
-        filename = ".brightway2path"
-    with open(os.path.join(user_dir, filename), "w") as f:
-        f.write(dirpath)
-
-    config.reset()
-    config.is_temp_dir = False
-    config.create_basic_directories()
-    reset_meta()
+    set_data_dir(os.path.join(path, dirname))
     return "1"
 
 
 @app.route('/start/biosphere')
 def install_biosphere():
-    # Download and format data
-    keys, values = JsonWrapper.loads(
-        requests.get("http://mutel.org/biosphere.json").content)
-    data = dict(zip([tuple(x) for x in keys], values))
-    biosphere = Database("biosphere")
-    biosphere.register(
-        format=["Handmade", -1],
-        depends=[],
-        num_processes=len(data),
-        )
-    biosphere.write(data)
-    biosphere.process()
+    setup()
     return "1"
 
 
         cpu_count = config.p.get("cpu_cores", None)
         report = SerializedLCAReport(fu, method, iterations, cpu_count)
         report.calculate()
-        if config.p.get("upload_reports", False) and \
-                config.p.get("report_server_url", None):
-            url = config.p["report_server_url"]
-            if url[-1] != "/":
-                url += "/"
-            r = requests.post(url + "upload",
-                data=JsonWrapper.dumps(report.report),
-                headers={'content-type': 'application/json'}
-                )
-            if r.status_code == 200:
-                report.report["metadata"]["online"] = url + "report/" + report.uuid
+        try:
+            report.upload()
+        except:
+            # No online report no cry
+            pass
         report.write()
         return report.uuid
 

File bw2ui/web/static/js/stepped-histogram.js

     x_max = d3.max(data.histogram, function (d) { return d[0]; }),
     x_min = d3.min(data.histogram, function (d) { return d[0]; }),
     median = data.statistics.median,
-    upper = data.statistics.interval[0],
-    lower = data.statistics.interval[1];
+    lower = data.statistics.interval[0],
+    upper = data.statistics.interval[1];
 
   var y = d3.scale.linear()
     .range([h - padding, padding])
   var indicator_line = d3.svg.line()
     .x(function(d) { return x(d[0]); })
     .y(function(d) { return y(d[1]); });
-  
+
   svg.append("svg:g")
     .append("svg:path")
     .attr("class", "indicator")
     .attr("x", w)
     .attr("y", h - padding - 5)
     .text(xlabel);
-}
+}

File bw2ui/web/templates/base.html

 	<script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.9.4/jquery.dataTables.min.js"></script>
 	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.9.4/css/jquery.dataTables.css" type="text/css" media="screen, projection">
 	<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.9.0/themes/flick/jquery-ui.css" type="text/css" media="screen, projection">
-	<script src="http://d3js.org/d3.v2.min.js"></script>
+	<script src="http://d3js.org/d3.v3.min.js"></script>
 	{% block extrahead %}{% endblock %}
 </head>
 <body>
 		{% block body %}{% endblock %}
 	</div>
 </body>
-</html>
+</html>

File bw2ui/web/templates/report.html

 <script src="{{ url_for('static', filename="js/hinton.js") }}"></script>
 <script src="{{ url_for('static', filename="js/stepped-histogram.js") }}"></script>
 <script src="{{ url_for('static', filename="js/hinton.js") }}"></script>
+<script src="{{ url_for('static', filename="js/force-directed.js") }}"></script>
 {% endblock %}
 
 {% block body %}
 	<p class="large"><span style="font-size: 2.5em; color:#000" id="ia-score"></span> <span class="ia-unit"></span></p>
 </div>
 <hr>
-<h1 style="margin-bottom: 0">Monte Carlo results</h1>
-<div class="span-7">
-	<h2 style="margin-bottom: 0">Median</h2>
-	<p class="large"><span style="font-size: 2.5em; color:#000" id="mc-median"></span> <span class="ia-unit"></span></p>
-	<h2 style="margin-bottom: 0">Average</h2>
-	<p class="large"><span style="font-size: 2.5em; color:#000" id="mc-mean"></span> <span class="ia-unit"></span></p>
-	<h2 style="margin-bottom: 0">95% density interval</h2>
-	<p class="large"><span style="font-size: 2.5em; color:#000"> <span id="mc-lower"></span>:<span id="mc-upper"></span></span> <span class="ia-unit"></span></p>
+<div id="mc-wrapper">
+  <h1 style="margin-bottom: 0">Monte Carlo results</h1>
+  <div class="span-7">
+  	<h2 style="margin-bottom: 0">Median</h2>
+  	<p class="large"><span style="font-size: 2.5em; color:#000" id="mc-median"></span> <span class="ia-unit"></span></p>
+  	<h2 style="margin-bottom: 0">Average</h2>
+  	<p class="large"><span style="font-size: 2.5em; color:#000" id="mc-mean"></span> <span class="ia-unit"></span></p>
+  	<h2 style="margin-bottom: 0">95% density interval</h2>
+  	<p class="large"><span style="font-size: 2.5em; color:#000"> <span id="mc-lower"></span>:<span id="mc-upper"></span></span> <span class="ia-unit"></span></p>
+  </div>
+  <div class="span-17 last" id="ihist"></div>
+  <hr>
 </div>
-<div class="span-17 last" id="ihist"></div>
+<div class="span-24">
+  <h1 style="margin-bottom: 0">Treemap</h1>
+  <div id="treemap"></div>
+</div>
 <hr>
-<div class="span-24" id="treemap"></div>
-<hr>
-<div class="span-14" id="hinton"></div>
+<div id="fd-wrapper">
+  <h1 style="margin-bottom: 0">Force-directed graph</h1>
+  <button id="switch-fd-graph-state">Individual weights</button>
+  <div class="span-24" id="fd-graph"></div>
+  <hr>
+</div>
+<div class="span-24"></div>
+<h1 style="margin-bottom: 0">Hinton matrix</h1>
+<div class="span-14" id="hinton">
+</div>
 <div class="span-10 last">
 	<h2 style="margin-bottom: 0"><a href="http://en.wikipedia.org/wiki/Concentration_ratio">Concentration index</a></h2>
 	<p class="large" style="font-size: 2.5em; color:#000; margin-bottom: 0" id="concentration-ratio"></p>
 <script type="text/javascript">
 $(document).ready( function() {
   var report_data = {{ data|safe }},
-    activity = "";
+    activity = "",
+    w = 950,
+    h = 800,
+    max_size = 40,
+    min_size = 6,
+    color_scale = d3.scale.category20();
 
   // Replace DOM elements with correct content
   $('#herfindahl').html(report_data.contribution.herfindahl.toPrecision(2));
   $('.ia-unit').html(report_data.method.unit);
   $('#method-name').html(report_data.method.name);
   $('#ia-score').html(report_data.score.toPrecision(2));
-  $('#mc-median').html(report_data["monte carlo"].statistics.median.toPrecision(2));
-  $('#mc-mean').html(report_data["monte carlo"].statistics.mean.toPrecision(2));
-  $('#mc-lower').html(report_data["monte carlo"].statistics.interval[0].toPrecision(2));
-  $('#mc-upper').html(report_data["monte carlo"].statistics.interval[1].toPrecision(2));
+
+  if ("online" in report_data.metadata) {
+    $("#online-report").html('<p class="success">This report also available online: <a href="' + report_data.metadata.online + '">' + report_data.metadata.online + '</a></p>');
+  };
+
+  if (report_data["monte carlo"] !== null && report_data["monte carlo"] !== undefined) {
+    $('#mc-median').html(report_data["monte carlo"].statistics.median.toPrecision(4));
+    $('#mc-mean').html(report_data["monte carlo"].statistics.mean.toPrecision(4));
+    $('#mc-lower').html(report_data["monte carlo"].statistics.interval[0].toPrecision(4));
+    $('#mc-upper').html(report_data["monte carlo"].statistics.interval[1].toPrecision(4));
+    stepped_histogram(report_data["monte carlo"], report_data.method.unit, "#ihist", 680, 300, 10);
+  } else {
+    $("#mc-wrapper").hide();
+  };
+
   for (var i = report_data.activity.length - 1; i >= 0; i--) {
     activity = activity + "<li class=&quotlarge&quot style=&quotmargin-bottom: 0; line-height: 1.1em&quot>" + report_data.activity[i][0] + ": " + report_data.activity[i][1] + " " + report_data.activity[i][2] + "</li>"
   };
   $('#activity').html(activity);
 
-  if ("online" in report_data.metadata) {
-    $("#online-report").html('<p class="success">This report also available online: <a href="' + report_data.metadata.online + '">' + report_data.metadata.online + '</a></p>');
+  if (report_data.force_directed !== undefined && report_data.force_directed !== null) {
+    force_directed_graph(report_data.force_directed, color_scale, 950, 600, 6, 40, "#fd-graph", "#switch-fd-graph-state")
+  } else {
+    $("#fd-wrapper").hide();
   };
 
   // Insert graphics
   hinton_matrix(report_data.contribution.hinton.results, report_data.contribution.hinton.total, report_data.contribution.hinton.xlabels, report_data.contribution.hinton.ylabels, "#hinton", 560, 560, 10);
-  stepped_histogram(report_data["monte carlo"], report_data.method.unit, "#ihist", 680, 300, 10);
 
   var width = 950,
       height = 200,
       .style("width", width + "px")
       .style("height", height + "px");
 
-  var cell_color_scale = d3.scale.category20();
-
   div.data([report_data.contribution.treemap]).selectAll("div")
     .data(treemap.nodes)
   .enter().append("div")
     .attr("class", "cell")
     .attr("title", function(d) { return d.children ? null : d.name; })
     .style("font-size", "14px")
-    .style("background", function(d, i) { return cell_color_scale(i % 20); }) // d.children ? color(d.name) : null; })
+    .style("background", function(d, i) { return color_scale(i % 20); }) // d.children ? color(d.name) : null; })
     .call(cell)
     .text(function(d) { return d.children ? null : d.name; });
 

File bw2ui/web/templates/start.html

   <h1>Great, that worked!</h1>
   <h3>Let's import the basic data needed for Brightway2</h3>
   <p>Please be patient - this might take a bit, depending on your internet connection...</p>
-  <button id="import-biosphere">Import basic metadata</button>
+  <button id="import-biosphere-button">Import basic metadata</button>
 </div>
 
 <div id="success" class="span-24 clear">
 };
 </script>
 <script src="{{ url_for('static', filename="jqueryFileTree/jqueryFileTree.js") }}"></script>
-<script type="text/javascript">      
-$(document).ready( function() {        
+<script type="text/javascript">
+$(document).ready( function() {
   $("#submit-path").hide();
   $("#import-biosphere").hide();
   $("#success").hide();
 
   $('#fp').fileTree({
-    root: '/', 
-    script: '/fp-api', 
-    folderEvent: 'click', 
-    multiFolder: false 
-    }, 
-    function(file) { 
+    root: '/',
+    script: '/fp-api',
+    folderEvent: 'click',
+    multiFolder: false
+    },
+    function(file) {
       console.log(file);
     }
   );
 
-  $('#import-biosphere').click(function () {
+  $('#import-biosphere-button').click(function () {
+    $("#import-biosphere-button").hide();
     $.ajax({
       type: "GET",
       url: "/start/biosphere",
   });
 });
 </script>
-{% endblock %}
+{% endblock %}
 
 setup(
   name='bw2ui',
-  version="0.5.1",
+  version="0.6.0-alpha",
   packages=["bw2ui", "bw2ui.web"],
   package_data={'bw2ui.web': [
     "static/blueprint/*.css",