Skip to content

Instantly share code, notes, and snippets.

@k-funk
Last active December 16, 2015 22:20
Show Gist options
  • Select an option

  • Save k-funk/5506481 to your computer and use it in GitHub Desktop.

Select an option

Save k-funk/5506481 to your computer and use it in GitHub Desktop.

Revisions

  1. k-funk revised this gist May 15, 2013. 1 changed file with 2617 additions and 1 deletion.
    2,618 changes: 2,617 additions & 1 deletion us_foreign_military_assistance_constant_flipped.json
    2,617 additions, 1 deletion not shown because the diff is too large. Please use a local Git client to view these changes.
  2. k-funk revised this gist May 15, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -78,7 +78,7 @@ <h3>Values are in constant dollars (adjusted for inflation). Part of 1976 omitte


    //json & drawing of map
    d3.json("world.geo.json-master/edit_countries.geo.json", function(error, content) {
    d3.json("edit_countries.geo.json", function(error, content) {

    country_boundaries = content.features;

    @@ -99,7 +99,7 @@ <h3>Values are in constant dollars (adjusted for inflation). Part of 1976 omitte


    //foreign aid data + drawing line chart
    d3.json("data/us_foreign_military_assistance_constant_flipped.json", function(error, country_data) {
    d3.json("us_foreign_military_assistance_constant_flipped.json", function(error, country_data) {

    //store totals in an array
    var totals = new Array();
  3. k-funk revised this gist May 15, 2013. 2 changed files with 3 additions and 2619 deletions.
    4 changes: 2 additions & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -2,8 +2,8 @@
    <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>Final - KFunk</title>
    <!-- <script src="http://d3js.org/d3.v3.min.js"></script> -->
    <script src="d3/d3.v3.min.js"></script>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <!-- <script src="d3/d3.v3.min.js"></script> -->

    </head>
    <link rel="stylesheet" href="style.css" type="text/css" media="screen" title="no title" charset="utf-8">
    2,618 changes: 1 addition & 2,617 deletions us_foreign_military_assistance_constant_flipped.json
    1 addition, 2,617 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  4. k-funk revised this gist May 15, 2013. 5 changed files with 12862 additions and 13581 deletions.
    17 changes: 16 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,18 @@
    # US FOREIGN MILITARY ASSISTANCE: 1947-2011#
    ####Values are in constant dollars (adjusted for inflation). Part of 1976 omitted, "fiscal year" months changed.####

    Values are in constant dollars (adjusted for inflation). Part of 1976 omitted, "fiscal year" months changed.
    ###Data Source:###
    https://explore.data.gov/Foreign-Commerce-and-Aid/U-S-Overseas-Loans-and-Grants-Greenbook-/5gah-bvex
    This data set contains the amount of US Dollars spent on military foreign aid, per country, for each year (1947-2011)
    The dataset has been altered in the following ways: Converted to .json, added the total foreign aid from all countries for a given year, and added 3 letter country codes for geospacial mapping references.
    JSON structure: {foregin aid[year,total_aid,countries{country name, country code, foreign aid received}]}
    I chose this data to visualize because it gives insight as to the US's rapidly changing policy on foreign aid for the past 60 years. Also, being able to visually see which countries are receiving the most money is informative as to the country's affairs of that year.

    ###Guide:###
    (1) Users will either start at the right sidebar, to see the the top countries for the initial year, 1947 or (2) choose a year from the bottom bar by hovering over the timeline, followed by step 1.

    ###Technique:###
    Upon choosing a year from the timeline (context of all years), the country list will populate, ordered by aid amount. Countries will be colored relative to that current year. Blue countries are lowest relative to that year, and green is the highest. IE: in 2011, Afghanistan received the most aid, so it it pure green. In 1947, Taiwan received the most aid, so it is pure green. I chose to do it this way, as opposed to being relative to all-time values because the money given in 1947, even with inflation, was far less than today's aid, making those years seem to have nearly no data. All the countries would be blue for low aid years.

    ###Learning Outcomes:###
    I learned about which countries have been receiving lots of money from us for long periods of time. For example, Egypt has been amongst the top receiving countries for nearly three decades. You can see the results from prolonged wars, but the countires that continue to receive military foreign aid from the US without conflict is personally, a bit concerning–especially countries with which our governments are at odds with, such as Russia.
    1,139 changes: 0 additions & 1,139 deletions cubism.v1.js
    Original file line number Diff line number Diff line change
    @@ -1,1139 +0,0 @@
    (function(exports){
    var cubism = exports.cubism = {version: "1.3.0"};
    var cubism_id = 0;
    function cubism_identity(d) { return d; }
    cubism.option = function(name, defaultValue) {
    var values = cubism.options(name);
    return values.length ? values[0] : defaultValue;
    };

    cubism.options = function(name, defaultValues) {
    var options = location.search.substring(1).split("&"),
    values = [],
    i = -1,
    n = options.length,
    o;
    while (++i < n) {
    if ((o = options[i].split("="))[0] == name) {
    values.push(decodeURIComponent(o[1]));
    }
    }
    return values.length || arguments.length < 2 ? values : defaultValues;
    };
    cubism.context = function() {
    var context = new cubism_context,
    step = 1e4, // ten seconds, in milliseconds
    size = 1440, // four hours at ten seconds, in pixels
    start0, stop0, // the start and stop for the previous change event
    start1, stop1, // the start and stop for the next prepare event
    serverDelay = 5e3,
    clientDelay = 5e3,
    event = d3.dispatch("prepare", "beforechange", "change", "focus"),
    scale = context.scale = d3.time.scale().range([0, size]),
    timeout,
    focus;

    function update() {
    var now = Date.now();
    stop0 = new Date(Math.floor((now - serverDelay - clientDelay) / step) * step);
    start0 = new Date(stop0 - size * step);
    stop1 = new Date(Math.floor((now - serverDelay) / step) * step);
    start1 = new Date(stop1 - size * step);
    scale.domain([start0, stop0]);
    return context;
    }

    context.start = function() {
    if (timeout) clearTimeout(timeout);
    var delay = +stop1 + serverDelay - Date.now();

    // If we're too late for the first prepare event, skip it.
    if (delay < clientDelay) delay += step;

    timeout = setTimeout(function prepare() {
    stop1 = new Date(Math.floor((Date.now() - serverDelay) / step) * step);
    start1 = new Date(stop1 - size * step);
    event.prepare.call(context, start1, stop1);

    setTimeout(function() {
    scale.domain([start0 = start1, stop0 = stop1]);
    event.beforechange.call(context, start1, stop1);
    event.change.call(context, start1, stop1);
    event.focus.call(context, focus);
    }, clientDelay);

    timeout = setTimeout(prepare, step);
    }, delay);
    return context;
    };

    context.stop = function() {
    timeout = clearTimeout(timeout);
    return context;
    };

    timeout = setTimeout(context.start, 10);

    // Set or get the step interval in milliseconds.
    // Defaults to ten seconds.
    context.step = function(_) {
    if (!arguments.length) return step;
    step = +_;
    return update();
    };

    // Set or get the context size (the count of metric values).
    // Defaults to 1440 (four hours at ten seconds).
    context.size = function(_) {
    if (!arguments.length) return size;
    scale.range([0, size = +_]);
    return update();
    };

    // The server delay is the amount of time we wait for the server to compute a
    // metric. This delay may result from clock skew or from delays collecting
    // metrics from various hosts. Defaults to 4 seconds.
    context.serverDelay = function(_) {
    if (!arguments.length) return serverDelay;
    serverDelay = +_;
    return update();
    };

    // The client delay is the amount of additional time we wait to fetch those
    // metrics from the server. The client and server delay combined represent the
    // age of the most recent displayed metric. Defaults to 1 second.
    context.clientDelay = function(_) {
    if (!arguments.length) return clientDelay;
    clientDelay = +_;
    return update();
    };

    // Sets the focus to the specified index, and dispatches a "focus" event.
    context.focus = function(i) {
    event.focus.call(context, focus = i);
    return context;
    };

    // Add, remove or get listeners for events.
    context.on = function(type, listener) {
    if (arguments.length < 2) return event.on(type);

    event.on(type, listener);

    // Notify the listener of the current start and stop time, as appropriate.
    // This way, metrics can make requests for data immediately,
    // and likewise the axis can display itself synchronously.
    if (listener != null) {
    if (/^prepare(\.|$)/.test(type)) listener.call(context, start1, stop1);
    if (/^beforechange(\.|$)/.test(type)) listener.call(context, start0, stop0);
    if (/^change(\.|$)/.test(type)) listener.call(context, start0, stop0);
    if (/^focus(\.|$)/.test(type)) listener.call(context, focus);
    }

    return context;
    };

    d3.select(window).on("keydown.context-" + ++cubism_id, function() {
    switch (!d3.event.metaKey && d3.event.keyCode) {
    case 37: // left
    if (focus == null) focus = size - 1;
    if (focus > 0) context.focus(--focus);
    break;
    case 39: // right
    if (focus == null) focus = size - 2;
    if (focus < size - 1) context.focus(++focus);
    break;
    default: return;
    }
    d3.event.preventDefault();
    });

    return update();
    };

    function cubism_context() {}

    var cubism_contextPrototype = cubism.context.prototype = cubism_context.prototype;

    cubism_contextPrototype.constant = function(value) {
    return new cubism_metricConstant(this, +value);
    };
    cubism_contextPrototype.cube = function(host) {
    if (!arguments.length) host = "";
    var source = {},
    context = this;

    source.metric = function(expression) {
    return context.metric(function(start, stop, step, callback) {
    d3.json(host + "/1.0/metric"
    + "?expression=" + encodeURIComponent(expression)
    + "&start=" + cubism_cubeFormatDate(start)
    + "&stop=" + cubism_cubeFormatDate(stop)
    + "&step=" + step, function(data) {
    if (!data) return callback(new Error("unable to load data"));
    callback(null, data.map(function(d) { return d.value; }));
    });
    }, expression += "");
    };

    // Returns the Cube host.
    source.toString = function() {
    return host;
    };

    return source;
    };

    var cubism_cubeFormatDate = d3.time.format.iso;
    cubism_contextPrototype.graphite = function(host) {
    if (!arguments.length) host = "";
    var source = {},
    context = this;

    source.metric = function(expression) {
    var sum = "sum";

    var metric = context.metric(function(start, stop, step, callback) {
    var target = expression;

    // Apply the summarize, if necessary.
    if (step !== 1e4) target = "summarize(" + target + ",'"
    + (!(step % 36e5) ? step / 36e5 + "hour" : !(step % 6e4) ? step / 6e4 + "min" : step / 1e3 + "sec")
    + "','" + sum + "')";

    d3.text(host + "/render?format=raw"
    + "&target=" + encodeURIComponent("alias(" + target + ",'')")
    + "&from=" + cubism_graphiteFormatDate(start - 2 * step) // off-by-two?
    + "&until=" + cubism_graphiteFormatDate(stop - 1000), function(text) {
    if (!text) return callback(new Error("unable to load data"));
    callback(null, cubism_graphiteParse(text));
    });
    }, expression += "");

    metric.summarize = function(_) {
    sum = _;
    return metric;
    };

    return metric;
    };

    source.find = function(pattern, callback) {
    d3.json(host + "/metrics/find?format=completer"
    + "&query=" + encodeURIComponent(pattern), function(result) {
    if (!result) return callback(new Error("unable to find metrics"));
    callback(null, result.metrics.map(function(d) { return d.path; }));
    });
    };

    // Returns the graphite host.
    source.toString = function() {
    return host;
    };

    return source;
    };

    // Graphite understands seconds since UNIX epoch.
    function cubism_graphiteFormatDate(time) {
    return Math.floor(time / 1000);
    }

    // Helper method for parsing graphite's raw format.
    function cubism_graphiteParse(text) {
    var i = text.indexOf("|"),
    meta = text.substring(0, i),
    c = meta.lastIndexOf(","),
    b = meta.lastIndexOf(",", c - 1),
    a = meta.lastIndexOf(",", b - 1),
    start = meta.substring(a + 1, b) * 1000,
    step = meta.substring(c + 1) * 1000;
    return text
    .substring(i + 1)
    .split(",")
    .slice(1) // the first value is always None?
    .map(function(d) { return +d; });
    }
    cubism_contextPrototype.gangliaWeb = function(config) {
    var host = '',
    uriPathPrefix = '/ganglia2/';

    if (arguments.length) {
    if (config.host) {
    host = config.host;
    }

    if (config.uriPathPrefix) {
    uriPathPrefix = config.uriPathPrefix;

    /* Add leading and trailing slashes, as appropriate. */
    if( uriPathPrefix[0] != '/' ) {
    uriPathPrefix = '/' + uriPathPrefix;
    }

    if( uriPathPrefix[uriPathPrefix.length - 1] != '/' ) {
    uriPathPrefix += '/';
    }
    }
    }

    var source = {},
    context = this;

    source.metric = function(metricInfo) {

    /* Store the members from metricInfo into local variables. */
    var clusterName = metricInfo.clusterName,
    metricName = metricInfo.metricName,
    hostName = metricInfo.hostName,
    isReport = metricInfo.isReport || false,
    titleGenerator = metricInfo.titleGenerator ||
    /* Reasonable (not necessarily pretty) default for titleGenerator. */
    function(unusedMetricInfo) {
    /* unusedMetricInfo is, well, unused in this default case. */
    return ('clusterName:' + clusterName +
    ' metricName:' + metricName +
    (hostName ? ' hostName:' + hostName : ''));
    },
    onChangeCallback = metricInfo.onChangeCallback;

    /* Default to plain, simple metrics. */
    var metricKeyName = isReport ? 'g' : 'm';

    var gangliaWebMetric = context.metric(function(start, stop, step, callback) {

    function constructGangliaWebRequestQueryParams() {
    return ('c=' + clusterName +
    '&' + metricKeyName + '=' + metricName +
    (hostName ? '&h=' + hostName : '') +
    '&cs=' + start/1000 + '&ce=' + stop/1000 + '&step=' + step/1000 + '&graphlot=1');
    }

    d3.json(host + uriPathPrefix + 'graph.php?' + constructGangliaWebRequestQueryParams(),
    function(result) {
    if( !result ) {
    return callback(new Error("Unable to fetch GangliaWeb data"));
    }

    callback(null, result[0].data);
    });

    }, titleGenerator(metricInfo));

    gangliaWebMetric.toString = function() {
    return titleGenerator(metricInfo);
    };

    /* Allow users to run their custom code each time a gangliaWebMetric changes.
    *
    * TODO Consider abstracting away the naked Cubism call, and instead exposing
    * a callback that takes in the values array (maybe alongwith the original
    * start and stop 'naked' parameters), since it's handy to have the entire
    * dataset at your disposal (and users will likely implement onChangeCallback
    * primarily to get at this dataset).
    */
    if (onChangeCallback) {
    gangliaWebMetric.on('change', onChangeCallback);
    }

    return gangliaWebMetric;
    };

    // Returns the gangliaWeb host + uriPathPrefix.
    source.toString = function() {
    return host + uriPathPrefix;
    };

    return source;
    };

    function cubism_metric(context) {
    if (!(context instanceof cubism_context)) throw new Error("invalid context");
    this.context = context;
    }

    var cubism_metricPrototype = cubism_metric.prototype;

    cubism.metric = cubism_metric;

    cubism_metricPrototype.valueAt = function() {
    return NaN;
    };

    cubism_metricPrototype.alias = function(name) {
    this.toString = function() { return name; };
    return this;
    };

    cubism_metricPrototype.extent = function() {
    var i = 0,
    n = this.context.size(),
    value,
    min = Infinity,
    max = -Infinity;
    while (++i < n) {
    value = this.valueAt(i);
    if (value < min) min = value;
    if (value > max) max = value;
    }
    return [min, max];
    };

    cubism_metricPrototype.on = function(type, listener) {
    return arguments.length < 2 ? null : this;
    };

    cubism_metricPrototype.shift = function() {
    return this;
    };

    cubism_metricPrototype.on = function() {
    return arguments.length < 2 ? null : this;
    };

    cubism_contextPrototype.metric = function(request, name) {
    var context = this,
    metric = new cubism_metric(context),
    id = ".metric-" + ++cubism_id,
    start = -Infinity,
    stop,
    step = context.step(),
    size = context.size(),
    values = [],
    event = d3.dispatch("change"),
    listening = 0,
    fetching;

    // Prefetch new data into a temporary array.
    function prepare(start1, stop) {
    var steps = Math.min(size, Math.round((start1 - start) / step));
    if (!steps || fetching) return; // already fetched, or fetching!
    fetching = true;
    steps = Math.min(size, steps + cubism_metricOverlap);
    var start0 = new Date(stop - steps * step);
    request(start0, stop, step, function(error, data) {
    fetching = false;
    if (error) return console.warn(error);
    var i = isFinite(start) ? Math.round((start0 - start) / step) : 0;
    for (var j = 0, m = data.length; j < m; ++j) values[j + i] = data[j];
    event.change.call(metric, start, stop);
    });
    }

    // When the context changes, switch to the new data, ready-or-not!
    function beforechange(start1, stop1) {
    if (!isFinite(start)) start = start1;
    values.splice(0, Math.max(0, Math.min(size, Math.round((start1 - start) / step))));
    start = start1;
    stop = stop1;
    }

    //
    metric.valueAt = function(i) {
    return values[i];
    };

    //
    metric.shift = function(offset) {
    return context.metric(cubism_metricShift(request, +offset));
    };

    //
    metric.on = function(type, listener) {
    if (!arguments.length) return event.on(type);

    // If there are no listeners, then stop listening to the context,
    // and avoid unnecessary fetches.
    if (listener == null) {
    if (event.on(type) != null && --listening == 0) {
    context.on("prepare" + id, null).on("beforechange" + id, null);
    }
    } else {
    if (event.on(type) == null && ++listening == 1) {
    context.on("prepare" + id, prepare).on("beforechange" + id, beforechange);
    }
    }

    event.on(type, listener);

    // Notify the listener of the current start and stop time, as appropriate.
    // This way, charts can display synchronous metrics immediately.
    if (listener != null) {
    if (/^change(\.|$)/.test(type)) listener.call(context, start, stop);
    }

    return metric;
    };

    //
    if (arguments.length > 1) metric.toString = function() {
    return name;
    };

    return metric;
    };

    // Number of metric to refetch each period, in case of lag.
    var cubism_metricOverlap = 6;

    // Wraps the specified request implementation, and shifts time by the given offset.
    function cubism_metricShift(request, offset) {
    return function(start, stop, step, callback) {
    request(new Date(+start + offset), new Date(+stop + offset), step, callback);
    };
    }
    function cubism_metricConstant(context, value) {
    cubism_metric.call(this, context);
    value = +value;
    var name = value + "";
    this.valueOf = function() { return value; };
    this.toString = function() { return name; };
    }

    var cubism_metricConstantPrototype = cubism_metricConstant.prototype = Object.create(cubism_metric.prototype);

    cubism_metricConstantPrototype.valueAt = function() {
    return +this;
    };

    cubism_metricConstantPrototype.extent = function() {
    return [+this, +this];
    };
    function cubism_metricOperator(name, operate) {

    function cubism_metricOperator(left, right) {
    if (!(right instanceof cubism_metric)) right = new cubism_metricConstant(left.context, right);
    else if (left.context !== right.context) throw new Error("mismatch context");
    cubism_metric.call(this, left.context);
    this.left = left;
    this.right = right;
    this.toString = function() { return left + " " + name + " " + right; };
    }

    var cubism_metricOperatorPrototype = cubism_metricOperator.prototype = Object.create(cubism_metric.prototype);

    cubism_metricOperatorPrototype.valueAt = function(i) {
    return operate(this.left.valueAt(i), this.right.valueAt(i));
    };

    cubism_metricOperatorPrototype.shift = function(offset) {
    return new cubism_metricOperator(this.left.shift(offset), this.right.shift(offset));
    };

    cubism_metricOperatorPrototype.on = function(type, listener) {
    if (arguments.length < 2) return this.left.on(type);
    this.left.on(type, listener);
    this.right.on(type, listener);
    return this;
    };

    return function(right) {
    return new cubism_metricOperator(this, right);
    };
    }

    cubism_metricPrototype.add = cubism_metricOperator("+", function(left, right) {
    return left + right;
    });

    cubism_metricPrototype.subtract = cubism_metricOperator("-", function(left, right) {
    return left - right;
    });

    cubism_metricPrototype.multiply = cubism_metricOperator("*", function(left, right) {
    return left * right;
    });

    cubism_metricPrototype.divide = cubism_metricOperator("/", function(left, right) {
    return left / right;
    });
    cubism_contextPrototype.horizon = function() {
    var context = this,
    mode = "offset",
    buffer = document.createElement("canvas"),
    width = buffer.width = context.size(),
    height = buffer.height = 30,
    scale = d3.scale.linear().interpolate(d3.interpolateRound),
    metric = cubism_identity,
    extent = null,
    title = cubism_identity,
    format = d3.format(".2s"),
    colors = ["#08519c","#3182bd","#6baed6","#bdd7e7","#bae4b3","#74c476","#31a354","#006d2c"];

    function horizon(selection) {

    selection
    .on("mousemove.horizon", function() { context.focus(Math.round(d3.mouse(this)[0])); })
    .on("mouseout.horizon", function() { context.focus(null); });

    selection.append("canvas")
    .attr("width", width)
    .attr("height", height);

    selection.append("span")
    .attr("class", "title")
    .text(title);

    selection.append("span")
    .attr("class", "value");

    selection.each(function(d, i) {
    var that = this,
    id = ++cubism_id,
    metric_ = typeof metric === "function" ? metric.call(that, d, i) : metric,
    colors_ = typeof colors === "function" ? colors.call(that, d, i) : colors,
    extent_ = typeof extent === "function" ? extent.call(that, d, i) : extent,
    start = -Infinity,
    step = context.step(),
    canvas = d3.select(that).select("canvas"),
    span = d3.select(that).select(".value"),
    max_,
    m = colors_.length >> 1,
    ready;

    canvas.datum({id: id, metric: metric_});
    canvas = canvas.node().getContext("2d");

    function change(start1, stop) {
    canvas.save();

    // compute the new extent and ready flag
    var extent = metric_.extent();
    ready = extent.every(isFinite);
    if (extent_ != null) extent = extent_;

    // if this is an update (with no extent change), copy old values!
    var i0 = 0, max = Math.max(-extent[0], extent[1]);
    if (this === context) {
    if (max == max_) {
    i0 = width - cubism_metricOverlap;
    var dx = (start1 - start) / step;
    if (dx < width) {
    var canvas0 = buffer.getContext("2d");
    canvas0.clearRect(0, 0, width, height);
    canvas0.drawImage(canvas.canvas, dx, 0, width - dx, height, 0, 0, width - dx, height);
    canvas.clearRect(0, 0, width, height);
    canvas.drawImage(canvas0.canvas, 0, 0);
    }
    }
    start = start1;
    }

    // update the domain
    scale.domain([0, max_ = max]);

    // clear for the new data
    canvas.clearRect(i0, 0, width - i0, height);

    // record whether there are negative values to display
    var negative;

    // positive bands
    for (var j = 0; j < m; ++j) {
    canvas.fillStyle = colors_[m + j];

    // Adjust the range based on the current band index.
    var y0 = (j - m + 1) * height;
    scale.range([m * height + y0, y0]);
    y0 = scale(0);

    for (var i = i0, n = width, y1; i < n; ++i) {
    y1 = metric_.valueAt(i);
    if (y1 <= 0) { negative = true; continue; }
    if (y1 === undefined) continue;
    canvas.fillRect(i, y1 = scale(y1), 1, y0 - y1);
    }
    }

    if (negative) {
    // enable offset mode
    if (mode === "offset") {
    canvas.translate(0, height);
    canvas.scale(1, -1);
    }

    // negative bands
    for (var j = 0; j < m; ++j) {
    canvas.fillStyle = colors_[m - 1 - j];

    // Adjust the range based on the current band index.
    var y0 = (j - m + 1) * height;
    scale.range([m * height + y0, y0]);
    y0 = scale(0);

    for (var i = i0, n = width, y1; i < n; ++i) {
    y1 = metric_.valueAt(i);
    if (y1 >= 0) continue;
    canvas.fillRect(i, scale(-y1), 1, y0 - scale(-y1));
    }
    }
    }

    canvas.restore();
    }

    function focus(i) {
    if (i == null) i = width - 1;
    var value = metric_.valueAt(i);
    span.datum(value).text(isNaN(value) ? null : format);
    }

    // Update the chart when the context changes.
    context.on("change.horizon-" + id, change);
    context.on("focus.horizon-" + id, focus);

    // Display the first metric change immediately,
    // but defer subsequent updates to the canvas change.
    // Note that someone still needs to listen to the metric,
    // so that it continues to update automatically.
    metric_.on("change.horizon-" + id, function(start, stop) {
    change(start, stop), focus();
    if (ready) metric_.on("change.horizon-" + id, cubism_identity);
    });
    });
    }

    horizon.remove = function(selection) {

    selection
    .on("mousemove.horizon", null)
    .on("mouseout.horizon", null);

    selection.selectAll("canvas")
    .each(remove)
    .remove();

    selection.selectAll(".title,.value")
    .remove();

    function remove(d) {
    d.metric.on("change.horizon-" + d.id, null);
    context.on("change.horizon-" + d.id, null);
    context.on("focus.horizon-" + d.id, null);
    }
    };

    horizon.mode = function(_) {
    if (!arguments.length) return mode;
    mode = _ + "";
    return horizon;
    };

    horizon.height = function(_) {
    if (!arguments.length) return height;
    buffer.height = height = +_;
    return horizon;
    };

    horizon.metric = function(_) {
    if (!arguments.length) return metric;
    metric = _;
    return horizon;
    };

    horizon.scale = function(_) {
    if (!arguments.length) return scale;
    scale = _;
    return horizon;
    };

    horizon.extent = function(_) {
    if (!arguments.length) return extent;
    extent = _;
    return horizon;
    };

    horizon.title = function(_) {
    if (!arguments.length) return title;
    title = _;
    return horizon;
    };

    horizon.format = function(_) {
    if (!arguments.length) return format;
    format = _;
    return horizon;
    };

    horizon.colors = function(_) {
    if (!arguments.length) return colors;
    colors = _;
    return horizon;
    };

    return horizon;
    };
    cubism_contextPrototype.comparison = function() {
    var context = this,
    width = context.size(),
    height = 120,
    scale = d3.scale.linear().interpolate(d3.interpolateRound),
    primary = function(d) { return d[0]; },
    secondary = function(d) { return d[1]; },
    extent = null,
    title = cubism_identity,
    formatPrimary = cubism_comparisonPrimaryFormat,
    formatChange = cubism_comparisonChangeFormat,
    colors = ["#9ecae1", "#225b84", "#a1d99b", "#22723a"],
    strokeWidth = 1.5;

    function comparison(selection) {

    selection
    .on("mousemove.comparison", function() { context.focus(Math.round(d3.mouse(this)[0])); })
    .on("mouseout.comparison", function() { context.focus(null); });

    selection.append("canvas")
    .attr("width", width)
    .attr("height", height);

    selection.append("span")
    .attr("class", "title")
    .text(title);

    selection.append("span")
    .attr("class", "value primary");

    selection.append("span")
    .attr("class", "value change");

    selection.each(function(d, i) {
    var that = this,
    id = ++cubism_id,
    primary_ = typeof primary === "function" ? primary.call(that, d, i) : primary,
    secondary_ = typeof secondary === "function" ? secondary.call(that, d, i) : secondary,
    extent_ = typeof extent === "function" ? extent.call(that, d, i) : extent,
    div = d3.select(that),
    canvas = div.select("canvas"),
    spanPrimary = div.select(".value.primary"),
    spanChange = div.select(".value.change"),
    ready;

    canvas.datum({id: id, primary: primary_, secondary: secondary_});
    canvas = canvas.node().getContext("2d");

    function change(start, stop) {
    canvas.save();
    canvas.clearRect(0, 0, width, height);

    // update the scale
    var primaryExtent = primary_.extent(),
    secondaryExtent = secondary_.extent(),
    extent = extent_ == null ? primaryExtent : extent_;
    scale.domain(extent).range([height, 0]);
    ready = primaryExtent.concat(secondaryExtent).every(isFinite);

    // consistent overplotting
    var round = start / context.step() & 1
    ? cubism_comparisonRoundOdd
    : cubism_comparisonRoundEven;

    // positive changes
    canvas.fillStyle = colors[2];
    for (var i = 0, n = width; i < n; ++i) {
    var y0 = scale(primary_.valueAt(i)),
    y1 = scale(secondary_.valueAt(i));
    if (y0 < y1) canvas.fillRect(round(i), y0, 1, y1 - y0);
    }

    // negative changes
    canvas.fillStyle = colors[0];
    for (i = 0; i < n; ++i) {
    var y0 = scale(primary_.valueAt(i)),
    y1 = scale(secondary_.valueAt(i));
    if (y0 > y1) canvas.fillRect(round(i), y1, 1, y0 - y1);
    }

    // positive values
    canvas.fillStyle = colors[3];
    for (i = 0; i < n; ++i) {
    var y0 = scale(primary_.valueAt(i)),
    y1 = scale(secondary_.valueAt(i));
    if (y0 <= y1) canvas.fillRect(round(i), y0, 1, strokeWidth);
    }

    // negative values
    canvas.fillStyle = colors[1];
    for (i = 0; i < n; ++i) {
    var y0 = scale(primary_.valueAt(i)),
    y1 = scale(secondary_.valueAt(i));
    if (y0 > y1) canvas.fillRect(round(i), y0 - strokeWidth, 1, strokeWidth);
    }

    canvas.restore();
    }

    function focus(i) {
    if (i == null) i = width - 1;
    var valuePrimary = primary_.valueAt(i),
    valueSecondary = secondary_.valueAt(i),
    valueChange = (valuePrimary - valueSecondary) / valueSecondary;

    spanPrimary
    .datum(valuePrimary)
    .text(isNaN(valuePrimary) ? null : formatPrimary);

    spanChange
    .datum(valueChange)
    .text(isNaN(valueChange) ? null : formatChange)
    .attr("class", "value change " + (valueChange > 0 ? "positive" : valueChange < 0 ? "negative" : ""));
    }

    // Display the first primary change immediately,
    // but defer subsequent updates to the context change.
    // Note that someone still needs to listen to the metric,
    // so that it continues to update automatically.
    primary_.on("change.comparison-" + id, firstChange);
    secondary_.on("change.comparison-" + id, firstChange);
    function firstChange(start, stop) {
    change(start, stop), focus();
    if (ready) {
    primary_.on("change.comparison-" + id, cubism_identity);
    secondary_.on("change.comparison-" + id, cubism_identity);
    }
    }

    // Update the chart when the context changes.
    context.on("change.comparison-" + id, change);
    context.on("focus.comparison-" + id, focus);
    });
    }

    comparison.remove = function(selection) {

    selection
    .on("mousemove.comparison", null)
    .on("mouseout.comparison", null);

    selection.selectAll("canvas")
    .each(remove)
    .remove();

    selection.selectAll(".title,.value")
    .remove();

    function remove(d) {
    d.primary.on("change.comparison-" + d.id, null);
    d.secondary.on("change.comparison-" + d.id, null);
    context.on("change.comparison-" + d.id, null);
    context.on("focus.comparison-" + d.id, null);
    }
    };

    comparison.height = function(_) {
    if (!arguments.length) return height;
    height = +_;
    return comparison;
    };

    comparison.primary = function(_) {
    if (!arguments.length) return primary;
    primary = _;
    return comparison;
    };

    comparison.secondary = function(_) {
    if (!arguments.length) return secondary;
    secondary = _;
    return comparison;
    };

    comparison.scale = function(_) {
    if (!arguments.length) return scale;
    scale = _;
    return comparison;
    };

    comparison.extent = function(_) {
    if (!arguments.length) return extent;
    extent = _;
    return comparison;
    };

    comparison.title = function(_) {
    if (!arguments.length) return title;
    title = _;
    return comparison;
    };

    comparison.formatPrimary = function(_) {
    if (!arguments.length) return formatPrimary;
    formatPrimary = _;
    return comparison;
    };

    comparison.formatChange = function(_) {
    if (!arguments.length) return formatChange;
    formatChange = _;
    return comparison;
    };

    comparison.colors = function(_) {
    if (!arguments.length) return colors;
    colors = _;
    return comparison;
    };

    comparison.strokeWidth = function(_) {
    if (!arguments.length) return strokeWidth;
    strokeWidth = _;
    return comparison;
    };

    return comparison;
    };

    var cubism_comparisonPrimaryFormat = d3.format(".2s"),
    cubism_comparisonChangeFormat = d3.format("+.0%");

    function cubism_comparisonRoundEven(i) {
    return i & 0xfffffe;
    }

    function cubism_comparisonRoundOdd(i) {
    return ((i + 1) & 0xfffffe) - 1;
    }
    cubism_contextPrototype.axis = function() {
    var context = this,
    scale = context.scale,
    axis_ = d3.svg.axis().scale(scale);

    var format = context.step() < 6e4 ? cubism_axisFormatSeconds
    : context.step() < 864e5 ? cubism_axisFormatMinutes
    : cubism_axisFormatDays;

    function axis(selection) {
    var id = ++cubism_id,
    tick;

    var g = selection.append("svg")
    .datum({id: id})
    .attr("width", context.size())
    .attr("height", Math.max(28, -axis.tickSize()))
    .append("g")
    .attr("transform", "translate(0," + (axis_.orient() === "top" ? 27 : 4) + ")")
    .call(axis_);

    context.on("change.axis-" + id, function() {
    g.call(axis_);
    if (!tick) tick = d3.select(g.node().appendChild(g.selectAll("text").node().cloneNode(true)))
    .style("display", "none")
    .text(null);
    });

    context.on("focus.axis-" + id, function(i) {
    if (tick) {
    if (i == null) {
    tick.style("display", "none");
    g.selectAll("text").style("fill-opacity", null);
    } else {
    tick.style("display", null).attr("x", i).text(format(scale.invert(i)));
    var dx = tick.node().getComputedTextLength() + 6;
    g.selectAll("text").style("fill-opacity", function(d) { return Math.abs(scale(d) - i) < dx ? 0 : 1; });
    }
    }
    });
    }

    axis.remove = function(selection) {

    selection.selectAll("svg")
    .each(remove)
    .remove();

    function remove(d) {
    context.on("change.axis-" + d.id, null);
    context.on("focus.axis-" + d.id, null);
    }
    };

    return d3.rebind(axis, axis_,
    "orient",
    "ticks",
    "tickSubdivide",
    "tickSize",
    "tickPadding",
    "tickFormat");
    };

    var cubism_axisFormatSeconds = d3.time.format("%I:%M:%S %p"),
    cubism_axisFormatMinutes = d3.time.format("%I:%M %p"),
    cubism_axisFormatDays = d3.time.format("%B %d");
    cubism_contextPrototype.rule = function() {
    var context = this,
    metric = cubism_identity;

    function rule(selection) {
    var id = ++cubism_id;

    var line = selection.append("div")
    .datum({id: id})
    .attr("class", "line")
    .call(cubism_ruleStyle);

    selection.each(function(d, i) {
    var that = this,
    id = ++cubism_id,
    metric_ = typeof metric === "function" ? metric.call(that, d, i) : metric;

    if (!metric_) return;

    function change(start, stop) {
    var values = [];

    for (var i = 0, n = context.size(); i < n; ++i) {
    if (metric_.valueAt(i)) {
    values.push(i);
    }
    }

    var lines = selection.selectAll(".metric").data(values);
    lines.exit().remove();
    lines.enter().append("div").attr("class", "metric line").call(cubism_ruleStyle);
    lines.style("left", cubism_ruleLeft);
    }

    context.on("change.rule-" + id, change);
    metric_.on("change.rule-" + id, change);
    });

    context.on("focus.rule-" + id, function(i) {
    line.datum(i)
    .style("display", i == null ? "none" : null)
    .style("left", i == null ? null : cubism_ruleLeft);
    });
    }

    rule.remove = function(selection) {

    selection.selectAll(".line")
    .each(remove)
    .remove();

    function remove(d) {
    context.on("focus.rule-" + d.id, null);
    }
    };

    rule.metric = function(_) {
    if (!arguments.length) return metric;
    metric = _;
    return rule;
    };

    return rule;
    };

    function cubism_ruleStyle(line) {
    line
    .style("position", "absolute")
    .style("top", 0)
    .style("bottom", 0)
    .style("width", "1px")
    .style("pointer-events", "none");
    }

    function cubism_ruleLeft(i) {
    return i + "px";
    }
    })(this);
    329 changes: 257 additions & 72 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -1,122 +1,307 @@
    <!DOCTYPE html>
    <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>Final - KFunk</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script src="cubism.v1.js"></script>
    <!-- <script src="http://d3js.org/d3.v3.min.js"></script> -->
    <script src="d3/d3.v3.min.js"></script>

    </head>
    <link rel="stylesheet" href="style.css" type="text/css" media="screen" title="no title" charset="utf-8">
    <body>
    <div id="header">
    <h1>US Foreign Military Assistance: 1947-2011</h1>
    <h3>Values are in constant dollars (adjusted for inflation). Part of 1976 omitted, "fiscal year" months changed.</h3>
    </div>
    <div id="map">

    </div>
    <div id="country_list">

    </div>
    <div id="line_plot">

    </div>
    <div id="header">
    <h1>US Foreign Military Assistance In Dollars: 1947-2011</h1>
    <h3>Values are in constant dollars (adjusted for inflation). Part of 1976 omitted, "fiscal year" months changed.</h3><h3><a href="https://explore.data.gov/Foreign-Commerce-and-Aid/U-S-Overseas-Loans-and-Grants-Greenbook-/5gah-bvex">Source</a></h3>
    </div>
    <div id="map">

    <div id="given_year">year: <span class="year_text" style="font-size:3em">1947</span></div>
    </div>
    <div id="country_list">
    <p>$ OF AID:</p>

    </div>
    <div id="line_plot">

    </div>
    <script>
    //helper functions
    function numberWithCommas(x) {
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    }

    //Variables
    var map_width = 900,
    map_height = 430,
    map_centering = 40,
    line_svg_width = 1000,
    line_svg_height = 100,
    line_plot_margin = 10
    line_plot_width = line_svg_width - (line_plot_margin *2),
    line_plot_height = line_svg_height - (line_plot_margin *2);
    var map_width = 880,
    map_height = 430,
    map_centering = 40,
    line_svg_width = 1000,
    line_svg_height = 100,
    base_country_color = "#ddd";
    var line_plot_margins = {
    top: 10,
    right: 10,
    bottom: 20,
    left: 50
    };

    var line_plot_width = line_svg_width - line_plot_margins.left - line_plot_margins.right,
    line_plot_height = line_svg_height - line_plot_margins.top - line_plot_margins.bottom;

    var country_boundaries;
    var country_data;

    var projection = d3.geo.mercator()
    .scale((map_width + 1) / 2 / Math.PI)
    .translate([map_width / 2, map_height / 2 + map_centering])
    .precision(.1);
    .scale((map_width + 1) / 2 / Math.PI)
    .translate([map_width / 2, map_height / 2 + map_centering])
    .precision(.1);

    var path = d3.geo.path()
    .projection(projection);
    .projection(projection);

    var map_svg = d3.select("div#map").append("svg")
    .attr("width", map_width)
    .attr("height", map_height);
    .attr("width", map_width)
    .attr("height", map_height);

    var line_plot_svg = d3.select("div#line_plot").append("svg")
    .attr("width", line_svg_width)
    .attr("height", line_svg_height);
    .attr("width", line_svg_width)
    .attr("height", line_svg_height);

    var line_plot = line_plot_svg.append("svg:g")
    .attr("transform" , "translate(" + line_plot_margin + "," + (line_plot_margin) + ")");

    .attr("transform" , "translate(" + line_plot_margins.left + "," + line_plot_margins.top + ")");
    var line_plot_rect = line_plot.append("rect").style("fill", "#EEE").attr("width", line_plot_width).attr("height", line_plot_height);

    var selection_line = line_plot_svg.append('line')
    .attr('transform', 'translate(0,' + line_plot_margins.top + ')')
    .attr({'x1': 0, 'y1' : 0, 'x2': 0, 'y2': line_plot_height})
    .attr('class', 'selection_line')
    .style('stroke', 'red');
    var selection_line_x = 0;



    //json & drawing
    d3.json("edit_countries.geo.json", function(error, content) {

    //json & drawing of map
    d3.json("world.geo.json-master/edit_countries.geo.json", function(error, content) {

    country_boundaries = content.features;

    map_svg.selectAll('path')
    .data(country_boundaries)
    .enter().append('svg:path')
    .attr('class', 'country')
    .attr('id', function(d) { return d.id; })
    .attr('d', function(d) { return path(d); })
    .attr('class', 'country')
    .attr('id', function(d) { return d.id; })
    .attr('d', function(d) { return path(d); })
    .attr('fill', '#ccc')
    .append('svg:title')
    .text(function(d) { return d.properties.name; });
    .text(function(d) { return d.properties.name; });

    country_list = d3.select("div#country_list").selectAll('li');
    country_list.data(country_boundaries)
    .enter().append('li')
    .html(function(d) { return d.properties.name; })
    .on("mouseover", function(d) { return; })
    .on("mouseout", function(d) { return; });

    //debug
    console.log(country_boundaries);

    });

    d3.json("us_foreign_military_assistance_constant_flipped.json", function(error, country_data) {

    //store totals in an array to be used in the y_scale
    //foreign aid data + drawing line chart
    d3.json("data/us_foreign_military_assistance_constant_flipped.json", function(error, country_data) {

    //store totals in an array
    var totals = new Array();
    var years = new Array();
    var current_year_countries = new Array();
    var current_year_aid = new Array();

    country_data.foreign_aid.forEach(function(d,i) { totals[i]=d.total_aid; });
    country_data.foreign_aid.forEach(function(d,i) { years[i]=d.year; });

    //Scales
    line_x_scale = d3.scale.linear()
    .domain([0, totals.length])
    .range([0, line_plot_width]);
    .domain([d3.min(years), d3.max(years)])
    .range([0, line_plot_width]);

    line_y_scale = d3.scale.linear()
    .domain([0, d3.max(totals)])
    .range([line_plot_height, 0]);
    .domain([0, d3.max(totals)])
    .range([line_plot_height, 0]);

    line_x_scale_rev = d3.scale.linear()
    .domain([0, line_plot_width])
    .range([d3.min(years), d3.max(years)]);

    //Country List
    //TODO right now, 1947 is hardcoded. 1947 is the 0th element of the country_data.foreign_aid.
    current_year_countries = country_data.foreign_aid[0].countries;
    function compare_countries(c1,c2) {
    if (c1.aid > c2.aid)
    return -1;
    if (c1.aid < c2.aid)
    return 1;
    return 0;
    }
    current_year_countries = current_year_countries.sort(compare_countries);

    //debug
    console.log(current_year_countries);

    //populate column with countries, ordered
    add_country_list(current_year_countries);

    //Line calculation
    //line plot calculation
    var line = d3.svg.line()
    .x(function(d,i) { return line_x_scale(i); })
    .y(function(d,i) { return line_y_scale(totals[i]); })
    .interpolate("linear");
    .x(function(d,i) { return line_x_scale(years[i]); })
    .y(function(d,i) { return line_y_scale(totals[i]); })
    .interpolate("linear");

    line_plot.append("svg:path")
    .attr("d", line(totals));
    .attr("d", line(totals));

    // setup x-axis
    var x_axis = d3.svg.axis()
    .scale(line_x_scale)
    .tickFormat(d3.format("g"));

    });


    //cubism
    var context = cubism.context();

    line_plot_svg.append("div")
    .attr("class", "rule")
    .call(context.rule());

    // d3.select(self.frameElement).style("height", height + "px");
    line_plot.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0, " + line_plot_height + ")")
    .call(x_axis);

    // setup y-axis
    var y_axis = d3.svg.axis()
    .scale(line_y_scale)
    .orient("left")
    .tickFormat(function(d) { return d3.round(d * .000000001, 0); } );

    line_plot.append("g")
    .attr("class", "y axis")
    .call(y_axis);

    // add label to y-axis
    d3.select(".y.axis")
    .append("text")
    .text("USD in Billions")
    .style("fill", "#888")
    .attr("text-anchor", "middle")
    .attr("transform", "rotate(270, -35, " + (line_plot_height / 2) + ")")
    .attr("x", -35)
    .attr("y", line_plot_height / 2);

    // line_plot mouse movement. interactivity starts here
    //TODO why can't i use line_plot_rect instead of line_plot_svg?
    line_plot_rect.on('mousemove', function () {

    selection_line_x = d3.mouse(this)[0];

    d3.select(".year_text").html(d3.round(line_x_scale_rev(selection_line_x)));

    remove_country_list();

    //TODO get year from user-input, populate current_year_countries
    country_data.foreign_aid.forEach(function(d,i) {
    if(d.year == d3.round(line_x_scale_rev(selection_line_x))) {
    current_year_countries = country_data.foreign_aid[i].countries;
    }
    });

    function compare_countries(c1,c2) {
    if (c1.aid > c2.aid)
    return -1;
    if (c1.aid < c2.aid)
    return 1;
    return 0;
    }
    current_year_countries = current_year_countries.sort(compare_countries);

    //get all of the aid for each year, to be used by the color scale with d3.max
    current_year_countries.forEach(function(d,i) { current_year_aid[i]=d.aid; });

    //reset countries to base color
    map_svg.selectAll('path')
    .attr('fill', '#ccc');

    add_country_list(current_year_countries);

    });

    var draw = function () {
    selection_line
    .transition()
    .duration(18)
    .attrTween("x1", function (d, i, a) {
    return d3.interpolate(a, selection_line_x + line_plot_margins.left)})
    .attrTween("x2", function (d, i, a) {
    return d3.interpolate(a, selection_line_x + line_plot_margins.left)})
    .each('end', draw);
    };
    draw();

    var lastMove;
    function add_country_list(current_year_countries){

    if ( lastMove && Date.now() - lastMove < 550 ) {
    setTimeout(function(){add_country_list(current_year_countries);},600);

    } else {
    color_scale = d3.scale.linear()
    .domain([0,d3.max(current_year_aid)])
    .range(["#84e2f4","green"]);

    //TODO make the delay happen even when scrolling fast along the timeline
    d3.select("div#country_list").append('ol').selectAll('li')
    .data(current_year_countries)
    .enter().append('li')
    .html(function(d) { return d.name; })
    .style("opacity", 0)
    .on("mouseover", function(d) {

    d3.selectAll("#tooltip")
    .transition()
    .duration(300)
    .style("opacity", 0)
    .remove();

    //TODO needs to be positioned relative to the corresponding country
    d3.select("#map")
    .append("div")
    .attr("id", "tooltip")
    .style("opacity", 1)
    .html(d.name + "<br />$" + ((d.aid != null) ? numberWithCommas(d.aid): "0"));

    })
    .on("mouseout", function(d) {

    d3.selectAll("#tooltip")
    .transition()
    .duration(300)
    .style("opacity", 0)
    .remove();

    })
    .transition()
    .duration(300)
    .style("opacity", 1).delay(100)
    // .delay(function(d, i) { return i*10; })
    ;

    current_year_countries.forEach(function(d){
    if(d.aid != null){
    d3.select("#" + d.c_code)
    .attr("fill", color_scale(d.aid));
    }
    })

    lastMove = Date.now();
    }
    }

    function remove_country_list(){
    lastMove = Date.now();
    //TODO make the delay happen even when scrolling fast along the timeline
    d3.select("div#country_list").selectAll('li')
    .transition()
    .duration(300)
    .style("opacity", 0)
    // .delay(function(d, i) { return i * 3; })
    .remove();

    }

    });

    </script>
    </script>
    </body>
    </html>
    49 changes: 43 additions & 6 deletions style.css
    Original file line number Diff line number Diff line change
    @@ -11,7 +11,7 @@ article, aside, canvas, details, embed,
    figure, figcaption, footer, header, hgroup,
    menu, nav, output, ruby, section, summary,
    time, mark, audio, video {
    margin: 0;
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    @@ -26,7 +26,7 @@ footer, header, hgroup, menu, nav, section {
    body {
    line-height: 1;
    }
    ol, ul {
    ul {
    list-style: none;
    }
    blockquote, q {
    @@ -45,6 +45,9 @@ table {






    body{
    margin:0 auto;
    width:1000px;
    @@ -60,6 +63,9 @@ h3{
    color:#999;
    font-size:1.1em;
    }
    a{
    color:inherit;
    }

    #header{
    padding:15px 0;
    @@ -68,15 +74,16 @@ h3{

    #map {
    float:left;
    position:relative;
    }

    #map svg {
    background:white;
    }

    #map .country{
    #map path {
    stroke:white;
    fill:#b5dafc;
    /*fill:#ccc; d3 doesn't allow css and inline styles simultaneously, css takes over*/
    }

    #country_list{
    @@ -85,14 +92,19 @@ h3{
    width:80px;
    padding-top:10px;
    padding-right:10px;
    padding-left:10px;
    padding-left:30px;
    float:left;
    overflow:scroll;
    }
    #country_list p{
    font-weight: bold;
    font-size: 1.3em;
    padding-bottom:5px;
    }

    #country_list li{
    margin-bottom:5px;
    list-style-type: none;
    /*list-style-type: none;*/
    }
    #country_list li:hover{
    font-color:#b5dafc;
    @@ -112,4 +124,29 @@ h3{
    #line_plot path{
    fill:none;
    stroke:#555;
    }
    .axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
    }
    .selection_line{
    stroke: rgba(0, 0, 0, 0.6);
    shape-rendering: crispEdges;
    }
    div#given_year{
    position:absolute;
    bottom:25px;
    right:195px;
    font-size:2em;
    color:#666;
    }
    div#tooltip {
    position:absolute;
    bottom:30px;
    left:325px;
    background-color:white;
    border:3px solid #666;
    font-size:1.4em;
    padding:10px;
    }
    24,909 changes: 12,546 additions & 12,363 deletions us_foreign_military_assistance_constant_flipped.json
    12,546 additions, 12,363 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  5. k-funk revised this gist May 3, 2013. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    # US FOREIGN MILITARY ASSISTANCE: 1947-2011#

    Values are in constant dollars (adjusted for inflation). Part of 1976 omitted, "fiscal year" months changed.
  6. k-funk created this gist May 3, 2013.
    1,139 changes: 1,139 additions & 0 deletions cubism.v1.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1139 @@
    (function(exports){
    var cubism = exports.cubism = {version: "1.3.0"};
    var cubism_id = 0;
    function cubism_identity(d) { return d; }
    cubism.option = function(name, defaultValue) {
    var values = cubism.options(name);
    return values.length ? values[0] : defaultValue;
    };

    cubism.options = function(name, defaultValues) {
    var options = location.search.substring(1).split("&"),
    values = [],
    i = -1,
    n = options.length,
    o;
    while (++i < n) {
    if ((o = options[i].split("="))[0] == name) {
    values.push(decodeURIComponent(o[1]));
    }
    }
    return values.length || arguments.length < 2 ? values : defaultValues;
    };
    cubism.context = function() {
    var context = new cubism_context,
    step = 1e4, // ten seconds, in milliseconds
    size = 1440, // four hours at ten seconds, in pixels
    start0, stop0, // the start and stop for the previous change event
    start1, stop1, // the start and stop for the next prepare event
    serverDelay = 5e3,
    clientDelay = 5e3,
    event = d3.dispatch("prepare", "beforechange", "change", "focus"),
    scale = context.scale = d3.time.scale().range([0, size]),
    timeout,
    focus;

    function update() {
    var now = Date.now();
    stop0 = new Date(Math.floor((now - serverDelay - clientDelay) / step) * step);
    start0 = new Date(stop0 - size * step);
    stop1 = new Date(Math.floor((now - serverDelay) / step) * step);
    start1 = new Date(stop1 - size * step);
    scale.domain([start0, stop0]);
    return context;
    }

    context.start = function() {
    if (timeout) clearTimeout(timeout);
    var delay = +stop1 + serverDelay - Date.now();

    // If we're too late for the first prepare event, skip it.
    if (delay < clientDelay) delay += step;

    timeout = setTimeout(function prepare() {
    stop1 = new Date(Math.floor((Date.now() - serverDelay) / step) * step);
    start1 = new Date(stop1 - size * step);
    event.prepare.call(context, start1, stop1);

    setTimeout(function() {
    scale.domain([start0 = start1, stop0 = stop1]);
    event.beforechange.call(context, start1, stop1);
    event.change.call(context, start1, stop1);
    event.focus.call(context, focus);
    }, clientDelay);

    timeout = setTimeout(prepare, step);
    }, delay);
    return context;
    };

    context.stop = function() {
    timeout = clearTimeout(timeout);
    return context;
    };

    timeout = setTimeout(context.start, 10);

    // Set or get the step interval in milliseconds.
    // Defaults to ten seconds.
    context.step = function(_) {
    if (!arguments.length) return step;
    step = +_;
    return update();
    };

    // Set or get the context size (the count of metric values).
    // Defaults to 1440 (four hours at ten seconds).
    context.size = function(_) {
    if (!arguments.length) return size;
    scale.range([0, size = +_]);
    return update();
    };

    // The server delay is the amount of time we wait for the server to compute a
    // metric. This delay may result from clock skew or from delays collecting
    // metrics from various hosts. Defaults to 4 seconds.
    context.serverDelay = function(_) {
    if (!arguments.length) return serverDelay;
    serverDelay = +_;
    return update();
    };

    // The client delay is the amount of additional time we wait to fetch those
    // metrics from the server. The client and server delay combined represent the
    // age of the most recent displayed metric. Defaults to 1 second.
    context.clientDelay = function(_) {
    if (!arguments.length) return clientDelay;
    clientDelay = +_;
    return update();
    };

    // Sets the focus to the specified index, and dispatches a "focus" event.
    context.focus = function(i) {
    event.focus.call(context, focus = i);
    return context;
    };

    // Add, remove or get listeners for events.
    context.on = function(type, listener) {
    if (arguments.length < 2) return event.on(type);

    event.on(type, listener);

    // Notify the listener of the current start and stop time, as appropriate.
    // This way, metrics can make requests for data immediately,
    // and likewise the axis can display itself synchronously.
    if (listener != null) {
    if (/^prepare(\.|$)/.test(type)) listener.call(context, start1, stop1);
    if (/^beforechange(\.|$)/.test(type)) listener.call(context, start0, stop0);
    if (/^change(\.|$)/.test(type)) listener.call(context, start0, stop0);
    if (/^focus(\.|$)/.test(type)) listener.call(context, focus);
    }

    return context;
    };

    d3.select(window).on("keydown.context-" + ++cubism_id, function() {
    switch (!d3.event.metaKey && d3.event.keyCode) {
    case 37: // left
    if (focus == null) focus = size - 1;
    if (focus > 0) context.focus(--focus);
    break;
    case 39: // right
    if (focus == null) focus = size - 2;
    if (focus < size - 1) context.focus(++focus);
    break;
    default: return;
    }
    d3.event.preventDefault();
    });

    return update();
    };

    function cubism_context() {}

    var cubism_contextPrototype = cubism.context.prototype = cubism_context.prototype;

    cubism_contextPrototype.constant = function(value) {
    return new cubism_metricConstant(this, +value);
    };
    cubism_contextPrototype.cube = function(host) {
    if (!arguments.length) host = "";
    var source = {},
    context = this;

    source.metric = function(expression) {
    return context.metric(function(start, stop, step, callback) {
    d3.json(host + "/1.0/metric"
    + "?expression=" + encodeURIComponent(expression)
    + "&start=" + cubism_cubeFormatDate(start)
    + "&stop=" + cubism_cubeFormatDate(stop)
    + "&step=" + step, function(data) {
    if (!data) return callback(new Error("unable to load data"));
    callback(null, data.map(function(d) { return d.value; }));
    });
    }, expression += "");
    };

    // Returns the Cube host.
    source.toString = function() {
    return host;
    };

    return source;
    };

    var cubism_cubeFormatDate = d3.time.format.iso;
    cubism_contextPrototype.graphite = function(host) {
    if (!arguments.length) host = "";
    var source = {},
    context = this;

    source.metric = function(expression) {
    var sum = "sum";

    var metric = context.metric(function(start, stop, step, callback) {
    var target = expression;

    // Apply the summarize, if necessary.
    if (step !== 1e4) target = "summarize(" + target + ",'"
    + (!(step % 36e5) ? step / 36e5 + "hour" : !(step % 6e4) ? step / 6e4 + "min" : step / 1e3 + "sec")
    + "','" + sum + "')";

    d3.text(host + "/render?format=raw"
    + "&target=" + encodeURIComponent("alias(" + target + ",'')")
    + "&from=" + cubism_graphiteFormatDate(start - 2 * step) // off-by-two?
    + "&until=" + cubism_graphiteFormatDate(stop - 1000), function(text) {
    if (!text) return callback(new Error("unable to load data"));
    callback(null, cubism_graphiteParse(text));
    });
    }, expression += "");

    metric.summarize = function(_) {
    sum = _;
    return metric;
    };

    return metric;
    };

    source.find = function(pattern, callback) {
    d3.json(host + "/metrics/find?format=completer"
    + "&query=" + encodeURIComponent(pattern), function(result) {
    if (!result) return callback(new Error("unable to find metrics"));
    callback(null, result.metrics.map(function(d) { return d.path; }));
    });
    };

    // Returns the graphite host.
    source.toString = function() {
    return host;
    };

    return source;
    };

    // Graphite understands seconds since UNIX epoch.
    function cubism_graphiteFormatDate(time) {
    return Math.floor(time / 1000);
    }

    // Helper method for parsing graphite's raw format.
    function cubism_graphiteParse(text) {
    var i = text.indexOf("|"),
    meta = text.substring(0, i),
    c = meta.lastIndexOf(","),
    b = meta.lastIndexOf(",", c - 1),
    a = meta.lastIndexOf(",", b - 1),
    start = meta.substring(a + 1, b) * 1000,
    step = meta.substring(c + 1) * 1000;
    return text
    .substring(i + 1)
    .split(",")
    .slice(1) // the first value is always None?
    .map(function(d) { return +d; });
    }
    cubism_contextPrototype.gangliaWeb = function(config) {
    var host = '',
    uriPathPrefix = '/ganglia2/';

    if (arguments.length) {
    if (config.host) {
    host = config.host;
    }

    if (config.uriPathPrefix) {
    uriPathPrefix = config.uriPathPrefix;

    /* Add leading and trailing slashes, as appropriate. */
    if( uriPathPrefix[0] != '/' ) {
    uriPathPrefix = '/' + uriPathPrefix;
    }

    if( uriPathPrefix[uriPathPrefix.length - 1] != '/' ) {
    uriPathPrefix += '/';
    }
    }
    }

    var source = {},
    context = this;

    source.metric = function(metricInfo) {

    /* Store the members from metricInfo into local variables. */
    var clusterName = metricInfo.clusterName,
    metricName = metricInfo.metricName,
    hostName = metricInfo.hostName,
    isReport = metricInfo.isReport || false,
    titleGenerator = metricInfo.titleGenerator ||
    /* Reasonable (not necessarily pretty) default for titleGenerator. */
    function(unusedMetricInfo) {
    /* unusedMetricInfo is, well, unused in this default case. */
    return ('clusterName:' + clusterName +
    ' metricName:' + metricName +
    (hostName ? ' hostName:' + hostName : ''));
    },
    onChangeCallback = metricInfo.onChangeCallback;

    /* Default to plain, simple metrics. */
    var metricKeyName = isReport ? 'g' : 'm';

    var gangliaWebMetric = context.metric(function(start, stop, step, callback) {

    function constructGangliaWebRequestQueryParams() {
    return ('c=' + clusterName +
    '&' + metricKeyName + '=' + metricName +
    (hostName ? '&h=' + hostName : '') +
    '&cs=' + start/1000 + '&ce=' + stop/1000 + '&step=' + step/1000 + '&graphlot=1');
    }

    d3.json(host + uriPathPrefix + 'graph.php?' + constructGangliaWebRequestQueryParams(),
    function(result) {
    if( !result ) {
    return callback(new Error("Unable to fetch GangliaWeb data"));
    }

    callback(null, result[0].data);
    });

    }, titleGenerator(metricInfo));

    gangliaWebMetric.toString = function() {
    return titleGenerator(metricInfo);
    };

    /* Allow users to run their custom code each time a gangliaWebMetric changes.
    *
    * TODO Consider abstracting away the naked Cubism call, and instead exposing
    * a callback that takes in the values array (maybe alongwith the original
    * start and stop 'naked' parameters), since it's handy to have the entire
    * dataset at your disposal (and users will likely implement onChangeCallback
    * primarily to get at this dataset).
    */
    if (onChangeCallback) {
    gangliaWebMetric.on('change', onChangeCallback);
    }

    return gangliaWebMetric;
    };

    // Returns the gangliaWeb host + uriPathPrefix.
    source.toString = function() {
    return host + uriPathPrefix;
    };

    return source;
    };

    function cubism_metric(context) {
    if (!(context instanceof cubism_context)) throw new Error("invalid context");
    this.context = context;
    }

    var cubism_metricPrototype = cubism_metric.prototype;

    cubism.metric = cubism_metric;

    cubism_metricPrototype.valueAt = function() {
    return NaN;
    };

    cubism_metricPrototype.alias = function(name) {
    this.toString = function() { return name; };
    return this;
    };

    cubism_metricPrototype.extent = function() {
    var i = 0,
    n = this.context.size(),
    value,
    min = Infinity,
    max = -Infinity;
    while (++i < n) {
    value = this.valueAt(i);
    if (value < min) min = value;
    if (value > max) max = value;
    }
    return [min, max];
    };

    cubism_metricPrototype.on = function(type, listener) {
    return arguments.length < 2 ? null : this;
    };

    cubism_metricPrototype.shift = function() {
    return this;
    };

    cubism_metricPrototype.on = function() {
    return arguments.length < 2 ? null : this;
    };

    cubism_contextPrototype.metric = function(request, name) {
    var context = this,
    metric = new cubism_metric(context),
    id = ".metric-" + ++cubism_id,
    start = -Infinity,
    stop,
    step = context.step(),
    size = context.size(),
    values = [],
    event = d3.dispatch("change"),
    listening = 0,
    fetching;

    // Prefetch new data into a temporary array.
    function prepare(start1, stop) {
    var steps = Math.min(size, Math.round((start1 - start) / step));
    if (!steps || fetching) return; // already fetched, or fetching!
    fetching = true;
    steps = Math.min(size, steps + cubism_metricOverlap);
    var start0 = new Date(stop - steps * step);
    request(start0, stop, step, function(error, data) {
    fetching = false;
    if (error) return console.warn(error);
    var i = isFinite(start) ? Math.round((start0 - start) / step) : 0;
    for (var j = 0, m = data.length; j < m; ++j) values[j + i] = data[j];
    event.change.call(metric, start, stop);
    });
    }

    // When the context changes, switch to the new data, ready-or-not!
    function beforechange(start1, stop1) {
    if (!isFinite(start)) start = start1;
    values.splice(0, Math.max(0, Math.min(size, Math.round((start1 - start) / step))));
    start = start1;
    stop = stop1;
    }

    //
    metric.valueAt = function(i) {
    return values[i];
    };

    //
    metric.shift = function(offset) {
    return context.metric(cubism_metricShift(request, +offset));
    };

    //
    metric.on = function(type, listener) {
    if (!arguments.length) return event.on(type);

    // If there are no listeners, then stop listening to the context,
    // and avoid unnecessary fetches.
    if (listener == null) {
    if (event.on(type) != null && --listening == 0) {
    context.on("prepare" + id, null).on("beforechange" + id, null);
    }
    } else {
    if (event.on(type) == null && ++listening == 1) {
    context.on("prepare" + id, prepare).on("beforechange" + id, beforechange);
    }
    }

    event.on(type, listener);

    // Notify the listener of the current start and stop time, as appropriate.
    // This way, charts can display synchronous metrics immediately.
    if (listener != null) {
    if (/^change(\.|$)/.test(type)) listener.call(context, start, stop);
    }

    return metric;
    };

    //
    if (arguments.length > 1) metric.toString = function() {
    return name;
    };

    return metric;
    };

    // Number of metric to refetch each period, in case of lag.
    var cubism_metricOverlap = 6;

    // Wraps the specified request implementation, and shifts time by the given offset.
    function cubism_metricShift(request, offset) {
    return function(start, stop, step, callback) {
    request(new Date(+start + offset), new Date(+stop + offset), step, callback);
    };
    }
    function cubism_metricConstant(context, value) {
    cubism_metric.call(this, context);
    value = +value;
    var name = value + "";
    this.valueOf = function() { return value; };
    this.toString = function() { return name; };
    }

    var cubism_metricConstantPrototype = cubism_metricConstant.prototype = Object.create(cubism_metric.prototype);

    cubism_metricConstantPrototype.valueAt = function() {
    return +this;
    };

    cubism_metricConstantPrototype.extent = function() {
    return [+this, +this];
    };
    function cubism_metricOperator(name, operate) {

    function cubism_metricOperator(left, right) {
    if (!(right instanceof cubism_metric)) right = new cubism_metricConstant(left.context, right);
    else if (left.context !== right.context) throw new Error("mismatch context");
    cubism_metric.call(this, left.context);
    this.left = left;
    this.right = right;
    this.toString = function() { return left + " " + name + " " + right; };
    }

    var cubism_metricOperatorPrototype = cubism_metricOperator.prototype = Object.create(cubism_metric.prototype);

    cubism_metricOperatorPrototype.valueAt = function(i) {
    return operate(this.left.valueAt(i), this.right.valueAt(i));
    };

    cubism_metricOperatorPrototype.shift = function(offset) {
    return new cubism_metricOperator(this.left.shift(offset), this.right.shift(offset));
    };

    cubism_metricOperatorPrototype.on = function(type, listener) {
    if (arguments.length < 2) return this.left.on(type);
    this.left.on(type, listener);
    this.right.on(type, listener);
    return this;
    };

    return function(right) {
    return new cubism_metricOperator(this, right);
    };
    }

    cubism_metricPrototype.add = cubism_metricOperator("+", function(left, right) {
    return left + right;
    });

    cubism_metricPrototype.subtract = cubism_metricOperator("-", function(left, right) {
    return left - right;
    });

    cubism_metricPrototype.multiply = cubism_metricOperator("*", function(left, right) {
    return left * right;
    });

    cubism_metricPrototype.divide = cubism_metricOperator("/", function(left, right) {
    return left / right;
    });
    cubism_contextPrototype.horizon = function() {
    var context = this,
    mode = "offset",
    buffer = document.createElement("canvas"),
    width = buffer.width = context.size(),
    height = buffer.height = 30,
    scale = d3.scale.linear().interpolate(d3.interpolateRound),
    metric = cubism_identity,
    extent = null,
    title = cubism_identity,
    format = d3.format(".2s"),
    colors = ["#08519c","#3182bd","#6baed6","#bdd7e7","#bae4b3","#74c476","#31a354","#006d2c"];

    function horizon(selection) {

    selection
    .on("mousemove.horizon", function() { context.focus(Math.round(d3.mouse(this)[0])); })
    .on("mouseout.horizon", function() { context.focus(null); });

    selection.append("canvas")
    .attr("width", width)
    .attr("height", height);

    selection.append("span")
    .attr("class", "title")
    .text(title);

    selection.append("span")
    .attr("class", "value");

    selection.each(function(d, i) {
    var that = this,
    id = ++cubism_id,
    metric_ = typeof metric === "function" ? metric.call(that, d, i) : metric,
    colors_ = typeof colors === "function" ? colors.call(that, d, i) : colors,
    extent_ = typeof extent === "function" ? extent.call(that, d, i) : extent,
    start = -Infinity,
    step = context.step(),
    canvas = d3.select(that).select("canvas"),
    span = d3.select(that).select(".value"),
    max_,
    m = colors_.length >> 1,
    ready;

    canvas.datum({id: id, metric: metric_});
    canvas = canvas.node().getContext("2d");

    function change(start1, stop) {
    canvas.save();

    // compute the new extent and ready flag
    var extent = metric_.extent();
    ready = extent.every(isFinite);
    if (extent_ != null) extent = extent_;

    // if this is an update (with no extent change), copy old values!
    var i0 = 0, max = Math.max(-extent[0], extent[1]);
    if (this === context) {
    if (max == max_) {
    i0 = width - cubism_metricOverlap;
    var dx = (start1 - start) / step;
    if (dx < width) {
    var canvas0 = buffer.getContext("2d");
    canvas0.clearRect(0, 0, width, height);
    canvas0.drawImage(canvas.canvas, dx, 0, width - dx, height, 0, 0, width - dx, height);
    canvas.clearRect(0, 0, width, height);
    canvas.drawImage(canvas0.canvas, 0, 0);
    }
    }
    start = start1;
    }

    // update the domain
    scale.domain([0, max_ = max]);

    // clear for the new data
    canvas.clearRect(i0, 0, width - i0, height);

    // record whether there are negative values to display
    var negative;

    // positive bands
    for (var j = 0; j < m; ++j) {
    canvas.fillStyle = colors_[m + j];

    // Adjust the range based on the current band index.
    var y0 = (j - m + 1) * height;
    scale.range([m * height + y0, y0]);
    y0 = scale(0);

    for (var i = i0, n = width, y1; i < n; ++i) {
    y1 = metric_.valueAt(i);
    if (y1 <= 0) { negative = true; continue; }
    if (y1 === undefined) continue;
    canvas.fillRect(i, y1 = scale(y1), 1, y0 - y1);
    }
    }

    if (negative) {
    // enable offset mode
    if (mode === "offset") {
    canvas.translate(0, height);
    canvas.scale(1, -1);
    }

    // negative bands
    for (var j = 0; j < m; ++j) {
    canvas.fillStyle = colors_[m - 1 - j];

    // Adjust the range based on the current band index.
    var y0 = (j - m + 1) * height;
    scale.range([m * height + y0, y0]);
    y0 = scale(0);

    for (var i = i0, n = width, y1; i < n; ++i) {
    y1 = metric_.valueAt(i);
    if (y1 >= 0) continue;
    canvas.fillRect(i, scale(-y1), 1, y0 - scale(-y1));
    }
    }
    }

    canvas.restore();
    }

    function focus(i) {
    if (i == null) i = width - 1;
    var value = metric_.valueAt(i);
    span.datum(value).text(isNaN(value) ? null : format);
    }

    // Update the chart when the context changes.
    context.on("change.horizon-" + id, change);
    context.on("focus.horizon-" + id, focus);

    // Display the first metric change immediately,
    // but defer subsequent updates to the canvas change.
    // Note that someone still needs to listen to the metric,
    // so that it continues to update automatically.
    metric_.on("change.horizon-" + id, function(start, stop) {
    change(start, stop), focus();
    if (ready) metric_.on("change.horizon-" + id, cubism_identity);
    });
    });
    }

    horizon.remove = function(selection) {

    selection
    .on("mousemove.horizon", null)
    .on("mouseout.horizon", null);

    selection.selectAll("canvas")
    .each(remove)
    .remove();

    selection.selectAll(".title,.value")
    .remove();

    function remove(d) {
    d.metric.on("change.horizon-" + d.id, null);
    context.on("change.horizon-" + d.id, null);
    context.on("focus.horizon-" + d.id, null);
    }
    };

    horizon.mode = function(_) {
    if (!arguments.length) return mode;
    mode = _ + "";
    return horizon;
    };

    horizon.height = function(_) {
    if (!arguments.length) return height;
    buffer.height = height = +_;
    return horizon;
    };

    horizon.metric = function(_) {
    if (!arguments.length) return metric;
    metric = _;
    return horizon;
    };

    horizon.scale = function(_) {
    if (!arguments.length) return scale;
    scale = _;
    return horizon;
    };

    horizon.extent = function(_) {
    if (!arguments.length) return extent;
    extent = _;
    return horizon;
    };

    horizon.title = function(_) {
    if (!arguments.length) return title;
    title = _;
    return horizon;
    };

    horizon.format = function(_) {
    if (!arguments.length) return format;
    format = _;
    return horizon;
    };

    horizon.colors = function(_) {
    if (!arguments.length) return colors;
    colors = _;
    return horizon;
    };

    return horizon;
    };
    cubism_contextPrototype.comparison = function() {
    var context = this,
    width = context.size(),
    height = 120,
    scale = d3.scale.linear().interpolate(d3.interpolateRound),
    primary = function(d) { return d[0]; },
    secondary = function(d) { return d[1]; },
    extent = null,
    title = cubism_identity,
    formatPrimary = cubism_comparisonPrimaryFormat,
    formatChange = cubism_comparisonChangeFormat,
    colors = ["#9ecae1", "#225b84", "#a1d99b", "#22723a"],
    strokeWidth = 1.5;

    function comparison(selection) {

    selection
    .on("mousemove.comparison", function() { context.focus(Math.round(d3.mouse(this)[0])); })
    .on("mouseout.comparison", function() { context.focus(null); });

    selection.append("canvas")
    .attr("width", width)
    .attr("height", height);

    selection.append("span")
    .attr("class", "title")
    .text(title);

    selection.append("span")
    .attr("class", "value primary");

    selection.append("span")
    .attr("class", "value change");

    selection.each(function(d, i) {
    var that = this,
    id = ++cubism_id,
    primary_ = typeof primary === "function" ? primary.call(that, d, i) : primary,
    secondary_ = typeof secondary === "function" ? secondary.call(that, d, i) : secondary,
    extent_ = typeof extent === "function" ? extent.call(that, d, i) : extent,
    div = d3.select(that),
    canvas = div.select("canvas"),
    spanPrimary = div.select(".value.primary"),
    spanChange = div.select(".value.change"),
    ready;

    canvas.datum({id: id, primary: primary_, secondary: secondary_});
    canvas = canvas.node().getContext("2d");

    function change(start, stop) {
    canvas.save();
    canvas.clearRect(0, 0, width, height);

    // update the scale
    var primaryExtent = primary_.extent(),
    secondaryExtent = secondary_.extent(),
    extent = extent_ == null ? primaryExtent : extent_;
    scale.domain(extent).range([height, 0]);
    ready = primaryExtent.concat(secondaryExtent).every(isFinite);

    // consistent overplotting
    var round = start / context.step() & 1
    ? cubism_comparisonRoundOdd
    : cubism_comparisonRoundEven;

    // positive changes
    canvas.fillStyle = colors[2];
    for (var i = 0, n = width; i < n; ++i) {
    var y0 = scale(primary_.valueAt(i)),
    y1 = scale(secondary_.valueAt(i));
    if (y0 < y1) canvas.fillRect(round(i), y0, 1, y1 - y0);
    }

    // negative changes
    canvas.fillStyle = colors[0];
    for (i = 0; i < n; ++i) {
    var y0 = scale(primary_.valueAt(i)),
    y1 = scale(secondary_.valueAt(i));
    if (y0 > y1) canvas.fillRect(round(i), y1, 1, y0 - y1);
    }

    // positive values
    canvas.fillStyle = colors[3];
    for (i = 0; i < n; ++i) {
    var y0 = scale(primary_.valueAt(i)),
    y1 = scale(secondary_.valueAt(i));
    if (y0 <= y1) canvas.fillRect(round(i), y0, 1, strokeWidth);
    }

    // negative values
    canvas.fillStyle = colors[1];
    for (i = 0; i < n; ++i) {
    var y0 = scale(primary_.valueAt(i)),
    y1 = scale(secondary_.valueAt(i));
    if (y0 > y1) canvas.fillRect(round(i), y0 - strokeWidth, 1, strokeWidth);
    }

    canvas.restore();
    }

    function focus(i) {
    if (i == null) i = width - 1;
    var valuePrimary = primary_.valueAt(i),
    valueSecondary = secondary_.valueAt(i),
    valueChange = (valuePrimary - valueSecondary) / valueSecondary;

    spanPrimary
    .datum(valuePrimary)
    .text(isNaN(valuePrimary) ? null : formatPrimary);

    spanChange
    .datum(valueChange)
    .text(isNaN(valueChange) ? null : formatChange)
    .attr("class", "value change " + (valueChange > 0 ? "positive" : valueChange < 0 ? "negative" : ""));
    }

    // Display the first primary change immediately,
    // but defer subsequent updates to the context change.
    // Note that someone still needs to listen to the metric,
    // so that it continues to update automatically.
    primary_.on("change.comparison-" + id, firstChange);
    secondary_.on("change.comparison-" + id, firstChange);
    function firstChange(start, stop) {
    change(start, stop), focus();
    if (ready) {
    primary_.on("change.comparison-" + id, cubism_identity);
    secondary_.on("change.comparison-" + id, cubism_identity);
    }
    }

    // Update the chart when the context changes.
    context.on("change.comparison-" + id, change);
    context.on("focus.comparison-" + id, focus);
    });
    }

    comparison.remove = function(selection) {

    selection
    .on("mousemove.comparison", null)
    .on("mouseout.comparison", null);

    selection.selectAll("canvas")
    .each(remove)
    .remove();

    selection.selectAll(".title,.value")
    .remove();

    function remove(d) {
    d.primary.on("change.comparison-" + d.id, null);
    d.secondary.on("change.comparison-" + d.id, null);
    context.on("change.comparison-" + d.id, null);
    context.on("focus.comparison-" + d.id, null);
    }
    };

    comparison.height = function(_) {
    if (!arguments.length) return height;
    height = +_;
    return comparison;
    };

    comparison.primary = function(_) {
    if (!arguments.length) return primary;
    primary = _;
    return comparison;
    };

    comparison.secondary = function(_) {
    if (!arguments.length) return secondary;
    secondary = _;
    return comparison;
    };

    comparison.scale = function(_) {
    if (!arguments.length) return scale;
    scale = _;
    return comparison;
    };

    comparison.extent = function(_) {
    if (!arguments.length) return extent;
    extent = _;
    return comparison;
    };

    comparison.title = function(_) {
    if (!arguments.length) return title;
    title = _;
    return comparison;
    };

    comparison.formatPrimary = function(_) {
    if (!arguments.length) return formatPrimary;
    formatPrimary = _;
    return comparison;
    };

    comparison.formatChange = function(_) {
    if (!arguments.length) return formatChange;
    formatChange = _;
    return comparison;
    };

    comparison.colors = function(_) {
    if (!arguments.length) return colors;
    colors = _;
    return comparison;
    };

    comparison.strokeWidth = function(_) {
    if (!arguments.length) return strokeWidth;
    strokeWidth = _;
    return comparison;
    };

    return comparison;
    };

    var cubism_comparisonPrimaryFormat = d3.format(".2s"),
    cubism_comparisonChangeFormat = d3.format("+.0%");

    function cubism_comparisonRoundEven(i) {
    return i & 0xfffffe;
    }

    function cubism_comparisonRoundOdd(i) {
    return ((i + 1) & 0xfffffe) - 1;
    }
    cubism_contextPrototype.axis = function() {
    var context = this,
    scale = context.scale,
    axis_ = d3.svg.axis().scale(scale);

    var format = context.step() < 6e4 ? cubism_axisFormatSeconds
    : context.step() < 864e5 ? cubism_axisFormatMinutes
    : cubism_axisFormatDays;

    function axis(selection) {
    var id = ++cubism_id,
    tick;

    var g = selection.append("svg")
    .datum({id: id})
    .attr("width", context.size())
    .attr("height", Math.max(28, -axis.tickSize()))
    .append("g")
    .attr("transform", "translate(0," + (axis_.orient() === "top" ? 27 : 4) + ")")
    .call(axis_);

    context.on("change.axis-" + id, function() {
    g.call(axis_);
    if (!tick) tick = d3.select(g.node().appendChild(g.selectAll("text").node().cloneNode(true)))
    .style("display", "none")
    .text(null);
    });

    context.on("focus.axis-" + id, function(i) {
    if (tick) {
    if (i == null) {
    tick.style("display", "none");
    g.selectAll("text").style("fill-opacity", null);
    } else {
    tick.style("display", null).attr("x", i).text(format(scale.invert(i)));
    var dx = tick.node().getComputedTextLength() + 6;
    g.selectAll("text").style("fill-opacity", function(d) { return Math.abs(scale(d) - i) < dx ? 0 : 1; });
    }
    }
    });
    }

    axis.remove = function(selection) {

    selection.selectAll("svg")
    .each(remove)
    .remove();

    function remove(d) {
    context.on("change.axis-" + d.id, null);
    context.on("focus.axis-" + d.id, null);
    }
    };

    return d3.rebind(axis, axis_,
    "orient",
    "ticks",
    "tickSubdivide",
    "tickSize",
    "tickPadding",
    "tickFormat");
    };

    var cubism_axisFormatSeconds = d3.time.format("%I:%M:%S %p"),
    cubism_axisFormatMinutes = d3.time.format("%I:%M %p"),
    cubism_axisFormatDays = d3.time.format("%B %d");
    cubism_contextPrototype.rule = function() {
    var context = this,
    metric = cubism_identity;

    function rule(selection) {
    var id = ++cubism_id;

    var line = selection.append("div")
    .datum({id: id})
    .attr("class", "line")
    .call(cubism_ruleStyle);

    selection.each(function(d, i) {
    var that = this,
    id = ++cubism_id,
    metric_ = typeof metric === "function" ? metric.call(that, d, i) : metric;

    if (!metric_) return;

    function change(start, stop) {
    var values = [];

    for (var i = 0, n = context.size(); i < n; ++i) {
    if (metric_.valueAt(i)) {
    values.push(i);
    }
    }

    var lines = selection.selectAll(".metric").data(values);
    lines.exit().remove();
    lines.enter().append("div").attr("class", "metric line").call(cubism_ruleStyle);
    lines.style("left", cubism_ruleLeft);
    }

    context.on("change.rule-" + id, change);
    metric_.on("change.rule-" + id, change);
    });

    context.on("focus.rule-" + id, function(i) {
    line.datum(i)
    .style("display", i == null ? "none" : null)
    .style("left", i == null ? null : cubism_ruleLeft);
    });
    }

    rule.remove = function(selection) {

    selection.selectAll(".line")
    .each(remove)
    .remove();

    function remove(d) {
    context.on("focus.rule-" + d.id, null);
    }
    };

    rule.metric = function(_) {
    if (!arguments.length) return metric;
    metric = _;
    return rule;
    };

    return rule;
    };

    function cubism_ruleStyle(line) {
    line
    .style("position", "absolute")
    .style("top", 0)
    .style("bottom", 0)
    .style("width", "1px")
    .style("pointer-events", "none");
    }

    function cubism_ruleLeft(i) {
    return i + "px";
    }
    })(this);
    182 changes: 182 additions & 0 deletions edit_countries.geo.json
    182 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    122 changes: 122 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,122 @@
    <!DOCTYPE html>
    <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>Final - KFunk</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script src="cubism.v1.js"></script>

    </head>
    <link rel="stylesheet" href="style.css" type="text/css" media="screen" title="no title" charset="utf-8">
    <body>
    <div id="header">
    <h1>US Foreign Military Assistance: 1947-2011</h1>
    <h3>Values are in constant dollars (adjusted for inflation). Part of 1976 omitted, "fiscal year" months changed.</h3>
    </div>
    <div id="map">

    </div>
    <div id="country_list">

    </div>
    <div id="line_plot">

    </div>
    <script>

    //Variables
    var map_width = 900,
    map_height = 430,
    map_centering = 40,
    line_svg_width = 1000,
    line_svg_height = 100,
    line_plot_margin = 10
    line_plot_width = line_svg_width - (line_plot_margin *2),
    line_plot_height = line_svg_height - (line_plot_margin *2);

    var country_boundaries;
    var country_data;

    var projection = d3.geo.mercator()
    .scale((map_width + 1) / 2 / Math.PI)
    .translate([map_width / 2, map_height / 2 + map_centering])
    .precision(.1);

    var path = d3.geo.path()
    .projection(projection);

    var map_svg = d3.select("div#map").append("svg")
    .attr("width", map_width)
    .attr("height", map_height);

    var line_plot_svg = d3.select("div#line_plot").append("svg")
    .attr("width", line_svg_width)
    .attr("height", line_svg_height);

    var line_plot = line_plot_svg.append("svg:g")
    .attr("transform" , "translate(" + line_plot_margin + "," + (line_plot_margin) + ")");





    //json & drawing
    d3.json("edit_countries.geo.json", function(error, content) {

    country_boundaries = content.features;

    map_svg.selectAll('path')
    .data(country_boundaries)
    .enter().append('svg:path')
    .attr('class', 'country')
    .attr('id', function(d) { return d.id; })
    .attr('d', function(d) { return path(d); })
    .append('svg:title')
    .text(function(d) { return d.properties.name; });

    country_list = d3.select("div#country_list").selectAll('li');
    country_list.data(country_boundaries)
    .enter().append('li')
    .html(function(d) { return d.properties.name; })
    .on("mouseover", function(d) { return; })
    .on("mouseout", function(d) { return; });

    });

    d3.json("us_foreign_military_assistance_constant_flipped.json", function(error, country_data) {

    //store totals in an array to be used in the y_scale
    var totals = new Array();
    country_data.foreign_aid.forEach(function(d,i) { totals[i]=d.total_aid; });

    //Scales
    line_x_scale = d3.scale.linear()
    .domain([0, totals.length])
    .range([0, line_plot_width]);
    line_y_scale = d3.scale.linear()
    .domain([0, d3.max(totals)])
    .range([line_plot_height, 0]);

    //Line calculation
    var line = d3.svg.line()
    .x(function(d,i) { return line_x_scale(i); })
    .y(function(d,i) { return line_y_scale(totals[i]); })
    .interpolate("linear");

    line_plot.append("svg:path")
    .attr("d", line(totals));

    });


    //cubism
    var context = cubism.context();

    line_plot_svg.append("div")
    .attr("class", "rule")
    .call(context.rule());

    // d3.select(self.frameElement).style("height", height + "px");

    </script>
    </body>
    </html>
    115 changes: 115 additions & 0 deletions style.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,115 @@
    html, body, div, span, applet, object, iframe,
    h1, h2, h3, h4, h5, h6, p, blockquote, pre,
    a, abbr, acronym, address, big, cite, code,
    del, dfn, em, img, ins, kbd, q, s, samp,
    small, strike, strong, sub, sup, tt, var,
    b, u, i, center,
    dl, dt, dd, ol, ul, li,
    fieldset, form, label, legend,
    table, caption, tbody, tfoot, thead, tr, th, td,
    article, aside, canvas, details, embed,
    figure, figcaption, footer, header, hgroup,
    menu, nav, output, ruby, section, summary,
    time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
    }
    /* HTML5 display-role reset for older browsers */
    article, aside, details, figcaption, figure,
    footer, header, hgroup, menu, nav, section {
    display: block;
    }
    body {
    line-height: 1;
    }
    ol, ul {
    list-style: none;
    }
    blockquote, q {
    quotes: none;
    }
    blockquote:before, blockquote:after,
    q:before, q:after {
    content: '';
    content: none;
    }
    table {
    border-collapse: collapse;
    border-spacing: 0;
    }
    /*END CSS RESET*/



    body{
    margin:0 auto;
    width:1000px;
    font-family: "Helvetica Neue", Helvetica, Arial, "MS Trebuchet", sans-serif;
    font-size:11px;
    }
    h1{
    font-size:2.3em;
    text-transform: uppercase;
    font-style:italic;
    }
    h3{
    color:#999;
    font-size:1.1em;
    }

    #header{
    padding:15px 0;
    border-bottom:1px solid #999;
    }

    #map {
    float:left;
    }

    #map svg {
    background:white;
    }

    #map .country{
    stroke:white;
    fill:#b5dafc;
    }

    #country_list{
    background-color:#eee;
    height:420px;
    width:80px;
    padding-top:10px;
    padding-right:10px;
    padding-left:10px;
    float:left;
    overflow:scroll;
    }

    #country_list li{
    margin-bottom:5px;
    list-style-type: none;
    }
    #country_list li:hover{
    font-color:#b5dafc;
    cursor:pointer;
    cursor:hand;
    }

    #line_plot{
    clear:both;
    float:left;
    width:1000px;
    background-color:#ededed;
    }
    #line_plot rect{
    fill:none;
    }
    #line_plot path{
    fill:none;
    stroke:#555;
    }
    12,819 changes: 12,819 additions & 0 deletions us_foreign_military_assistance_constant_flipped.json
    12,819 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.