# HG changeset patch # User Dennis Fink # Date 2015-07-26 00:23:10 # Node ID 51ac306b4e1a25665d924867bddf1d7a1ef0af8f # Parent d089bc401d2560eb5b250deed2d56369ff187db0 Added worldmap diff --git a/ennstatus/__init__.py b/ennstatus/__init__.py --- a/ennstatus/__init__.py +++ b/ennstatus/__init__.py @@ -59,6 +59,9 @@ def create_app(): from .status.views import status_page app.register_blueprint(status_page, url_prefix='/status') + from .statistics.views import statistics_page + app.register_blueprint(statistics_page, url_prefix='/stats') + from .log import init_logging init_logging(app) diff --git a/ennstatus/static/css/map.css b/ennstatus/static/css/map.css new file mode 100644 --- /dev/null +++ b/ennstatus/static/css/map.css @@ -0,0 +1,5 @@ +.country { + stroke: #000; + stroke-linejoin: round; + stroke-width: 0.1px; +} diff --git a/ennstatus/static/js/worldmap.js b/ennstatus/static/js/worldmap.js new file mode 100644 --- /dev/null +++ b/ennstatus/static/js/worldmap.js @@ -0,0 +1,152 @@ +d3.select(window).on("resize", throttle); + +var zoom = d3.behavior.zoom() + .scaleExtent([1, 9]) + .on("zoom", move); + +var width = document.getElementById('chart').offsetWidth; +var height = width / 2; + +var topo, projection, path, svg, g; + +var graticule = d3.geo.graticule(); + +var tooltip = d3.select("#chart").append("div").attr("class", "tooltip hidden"); + +setup(width, height); + +function setup(width, height) { + projection = d3.geo.mercator() + .translate([(width / 2), (height / 2)]) + .scale(width / 2 / Math.PI) + + path = d3.geo.path().projection(projection); + + svg = d3.select("#chart").append("svg") + .attr("width", width) + .attr("height", height) + .call(zoom) + .append("g"); + + g = svg.append("g") + .on("click", click); +} + +d3.json("/static/data/world-topo-min.json", function(error, world) { + var countries = topojson.feature(world, world.objects.countries).features; + + topo = countries; + draw(topo); +}); + +function draw(topo) { + + svg.append("path") + .datum(graticule) + .attr("class", "graticule") + .attr("d", path); + + g.append("path") + .datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]}) + .attr("class", "equator") + .attr("d", path); + + var country = g.selectAll(".country").data(topo); + d3.json("/stats/data/worldmap", function(err, data) { + + + var colorscale = d3.scale.threshold().domain([2, 4, 8, 16, 32, 48]).range(["#f2f0f7", "#dadaeb", "#bcbddc", "#9e9ac8", "#756bb1", "#54278f"]) + + country.enter().insert("path") + .attr("class", "country") + .attr("d", path) + .attr("id", function(d, i) { return d.id; }) + .attr("title", function(d, i) { + if (d.properties.name in data) { + return "

" + d.properties.name + "

" + data[d.properties.name]; + } else { + return d.properties.name; + } + }) + .style("fill", function(d, i) { + if (d.properties.name in data) { + return colorscale(data[d.properties.name]); + } else { + return "#fdf6e3"; + } + }); + + var offsetL = document.getElementById('chart').offsetLet+20; + var offsetT = document.getElementById('chart').offsetTop+10; + + country.on("mousemove", function(d, i) { + + var mouse = d3.mouse(svg.node()).map(function(d) { return parseInt(d); }); + + tooltip.classed("hidden", false) + .attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px") + .html(function(d, i) { + if (d.properties.name in data) { + return d.properties.name + " " + data[d.properties.name]; + } else { + return d.properties.name; + } + }) + }) + .on("mouseout", function(d, i) { + tooltip.classed("hidden", true); + }); + + + }); +}; + + +function redraw() { + width = document.getElementById('chart').offsetWidth; + height = width / 2; + d3.select('svg').remove(); + setup(width,height); + draw(topo); +} + + +function move() { + + var t = d3.event.translate; + var s = d3.event.scale; + zscale = s; + var h = height/4; + + + t[0] = Math.min( + (width/height) * (s - 1), + Math.max( width * (1 - s), t[0] ) + ); + + t[1] = Math.min( + h * (s - 1) + h * s, + Math.max(height * (1 - s) - h * s, t[1]) + ); + + zoom.translate(t); + g.attr("transform", "translate(" + t + ")scale(" + s + ")"); + + //adjust the country hover stroke width based on zoom level + d3.selectAll(".country").style("stroke-width", 1.5 / s); + +} + + +var throttleTimer; +function throttle() { + window.clearTimeout(throttleTimer); + throttleTimer = window.setTimeout(function() { + redraw(); + }, 200); +} + +function click() { + var latlon = projection.invert(d3.mouse(this)); + console.log(latlon); +} diff --git a/ennstatus/statistics/__init__.py b/ennstatus/statistics/__init__.py new file mode 100644 diff --git a/ennstatus/statistics/views.py b/ennstatus/statistics/views.py new file mode 100644 --- /dev/null +++ b/ennstatus/statistics/views.py @@ -0,0 +1,28 @@ +from collections import defaultdict + +from flask import Blueprint, render_template, current_app, jsonify + +from ennstatus.status.functions import split_all_servers_to_types + +statistics_page = Blueprint('statistics', __name__) + + +@statistics_page.route('/worldmap') +def worldmap(): + return render_template('statistics/worldmap.html') + + +@statistics_page.route('/data/worldmap') +def data_worldmap(): + servers = split_all_servers_to_types() + countries = defaultdict(int) + + for key, value in servers.items(): + for server in value: + countries[server['country']] += 1 + + maximum = max(countries.values()) + + countries['max'] = maximum + + return jsonify(countries) diff --git a/ennstatus/templates/statistics/worldmap.html b/ennstatus/templates/statistics/worldmap.html new file mode 100644 --- /dev/null +++ b/ennstatus/templates/statistics/worldmap.html @@ -0,0 +1,22 @@ +{% extends "base.html"%} + +{% set title = "index" %} + +{% block styles %} + {{ super() }} + +{% endblock %} + +{% block content %} +
+
+
+
+{% endblock %} + +{% block scripts %} + {{ super() }} + + + +{% endblock %}