Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active February 25, 2016 13:48

Revisions

  1. nitaku revised this gist Feb 25, 2016. 5 changed files with 94 additions and 26 deletions.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1 +1 @@
    -
    FIXME undefined data points are not represented correctly
    56 changes: 47 additions & 9 deletions index.coffee
    Original file line number Diff line number Diff line change
    @@ -2,9 +2,12 @@ MARGIN = 20
    MARGIN_LEFT = 20
    MARGIN_BOTTOM = 40
    keyword = 'cocaine'
    forum = 'drugsforum'

    # FIXME se la API li cambiano, devono restituire i nuovi valori. Se non lo dicono il valore di end dev'essere una tacca più avanti, altrimenti manca l'ultimo valore.
    START = 915148800
    #START = 915148800 #1999
    START = 946598400 #2000
    #START = 1041292800 #2003
    END = 1450310400

    #FIXME lo step non può essere fisso in secondi, ci vuole un'aggregazione per mesi o settimane veri
    @@ -36,20 +39,35 @@ line_generator = d3_shape.area()
    .curve d3_shape.stepAfter # step interpolator is the only one that can be applied after wrapping and wrap without error

    redraw = () ->
    d3.json "http://wafi.iit.cnr.it/cas-scraper2/api/timeseries/getTermTs.php?db=drugsforum&term=#{keyword}&start=#{START}&end=#{END}&step=#{STEP}", (result) ->
    start = new Date(d3.min(result.data, (d) -> d.date)*1000)
    end = new Date(d3.max(result.data, (d) -> d.date)*1000+(STEP*1000))
    d3.select('body').classed 'wait', true
    d3.json "http://wafi.iit.cnr.it/cas-scraper2/api/timeseries/getTermTs.php?db=#{forum}&term=#{keyword}&start=#{START}&end=#{END}&step=#{STEP}", (result) ->
    d3.select('body').classed 'wait', false

    start = new Date(START*1000)
    end = new Date(END*1000+(STEP*1000))

    index = {}
    result.data.forEach (d) ->
    d.date = new Date(+d.date*1000)
    d.date = 1000*d.date
    index[d.date] = d

    data = d3.range(+start, +end, STEP*1000).map (k) ->
    if k of index
    return index[k]
    else
    return {date: new Date(k), value: 0}

    data.forEach (d) ->
    d.date = new Date(d.date)

    x
    .domain([start, end])

    y
    .domain([0, d3.max(result.data, (d) -> d.value)])
    .domain([0, d3.max(data, (d) -> d.value)])

    # add a fake data point to extend the stepped curve to include the last value
    result.data.push {date: end, value: null}
    data.push {date: end, value: null}

    # define the x axis
    xAxis = d3.svg.axis()
    @@ -79,7 +97,7 @@ redraw = () ->
    .call(yAxis)

    chart.append('path')
    .datum result.data
    .datum data
    .attr
    class: 'area'
    d: line_generator
    @@ -90,7 +108,27 @@ d3.select("#keyword").node().value = keyword
    d3.select("#keyword").on 'keyup', () ->
    if(d3.event.keyCode == 13)
    keyword = this.value
    keyword = keyword.replace(" ", "%2B")
    .replace(/ and /gi, "%2BAND%2B") # URL encoding of queries
    .replace(/ or /gi, "%2BOR%2B")
    .replace(/-/g, "%2D")
    .replace(/ /g, "%2B")
    redraw()

    d3.select "#forum_ctrl"
    .on "change", () ->
    forum = this.options[this.selectedIndex].value
    redraw()

    # d3.select "#date_start"
    # .on "change", () ->
    # START = +new Date(this.value)/1000
    # console.log START
    # redraw()

    # d3.select "#date_end"
    # .on "change", () ->
    # END = +new Date(this.value)/1000
    # console.log END
    # redraw()

    redraw()
    3 changes: 3 additions & 0 deletions index.css
    Original file line number Diff line number Diff line change
    @@ -6,6 +6,9 @@ body, html {
    font-family: sans-serif;
    font-size: 12px;
    }
    body.wait {
    cursor: progress;
    }
    svg {
    width: 100%;
    height: 100%;
    8 changes: 8 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -9,6 +9,14 @@
    <script src="d3-shape.js"></script>
    </head>
    <body>
    <div>
    <label>Select source:</label>
    <select id="forum_ctrl">
    <option value="drugsforum">Drugs-forum</option>
    <option value="bluelight">Bluelight</option>
    <option value="tweets_NPS">Tweets</option>
    </select>
    </div>
    <div id="search">
    <label>Keyword:</label>
    <input type="search" id="keyword">
    51 changes: 35 additions & 16 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Generated by CoffeeScript 1.10.0
    (function() {
    var END, MARGIN, MARGIN_BOTTOM, MARGIN_LEFT, START, STEP, chart, chart_height, chart_width, height, keyword, line_generator, redraw, svg, width, x, y;
    var END, MARGIN, MARGIN_BOTTOM, MARGIN_LEFT, START, STEP, chart, chart_height, chart_width, forum, height, keyword, line_generator, redraw, svg, width, x, y;

    MARGIN = 20;

    @@ -10,7 +10,9 @@

    keyword = 'cocaine';

    START = 915148800;
    forum = 'drugsforum';

    START = 946598400;

    END = 1450310400;

    @@ -43,24 +45,37 @@
    }).curve(d3_shape.stepAfter);

    redraw = function() {
    return d3.json("http://wafi.iit.cnr.it/cas-scraper2/api/timeseries/getTermTs.php?db=drugsforum&term=" + keyword + "&start=" + START + "&end=" + END + "&step=" + STEP, function(result) {
    var end, start, xAxis, yAxis;
    start = new Date(d3.min(result.data, function(d) {
    return d.date;
    }) * 1000);
    end = new Date(d3.max(result.data, function(d) {
    return d.date;
    }) * 1000 + (STEP * 1000));
    d3.select('body').classed('wait', true);
    return d3.json("http://wafi.iit.cnr.it/cas-scraper2/api/timeseries/getTermTs.php?db=" + forum + "&term=" + keyword + "&start=" + START + "&end=" + END + "&step=" + STEP, function(result) {
    var data, end, index, start, xAxis, yAxis;
    d3.select('body').classed('wait', false);
    start = new Date(START * 1000);
    end = new Date(END * 1000 + (STEP * 1000));
    index = {};
    result.data.forEach(function(d) {
    return d.date = new Date(+d.date * 1000);
    d.date = 1000 * d.date;
    return index[d.date] = d;
    });
    data = d3.range(+start, +end, STEP * 1000).map(function(k) {
    if (k in index) {
    return index[k];
    } else {
    return {
    date: new Date(k),
    value: 0
    };
    }
    });
    data.forEach(function(d) {
    return d.date = new Date(d.date);
    });
    x.domain([start, end]);
    y.domain([
    0, d3.max(result.data, function(d) {
    0, d3.max(data, function(d) {
    return d.value;
    })
    ]);
    result.data.push({
    data.push({
    date: end,
    value: null
    });
    @@ -69,7 +84,7 @@
    chart.selectAll('*').remove();
    chart.append("g").attr("class", "xaxis").attr("transform", "translate(0," + (chart_height + 0.5) + ")").call(xAxis);
    chart.append("g").attr("class", "yaxis").call(yAxis);
    return chart.append('path').datum(result.data).attr({
    return chart.append('path').datum(data).attr({
    "class": 'area',
    d: line_generator,
    fill: 'orange'
    @@ -81,12 +96,16 @@

    d3.select("#keyword").on('keyup', function() {
    if (d3.event.keyCode === 13) {
    keyword = this.value;
    keyword = keyword.replace(" ", "%2B");
    keyword = this.value.replace(/ and /gi, "%2BAND%2B").replace(/ or /gi, "%2BOR%2B").replace(/-/g, "%2D").replace(/ /g, "%2B");
    return redraw();
    }
    });

    d3.select("#forum_ctrl").on("change", function() {
    forum = this.options[this.selectedIndex].value;
    return redraw();
    });

    redraw();

    }).call(this);
  2. nitaku revised this gist Dec 21, 2015. 8 changed files with 2080 additions and 0 deletions.
    154 changes: 154 additions & 0 deletions d3-path.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,154 @@
    (function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define('d3-path', ['exports'], factory) :
    factory((global.d3_path = {}));
    }(this, function (exports) { 'use strict';

    var pi = Math.PI;
    var tau = 2 * pi;
    var epsilon = 1e-6;
    var tauEpsilon = tau - epsilon;
    function Path() {
    this._x0 = this._y0 = // start of current subpath
    this._x1 = this._y1 = null; // end of current subpath
    this._ = [];
    }

    function path() {
    return new Path;
    }

    Path.prototype = path.prototype = {
    moveTo: function(x, y) {
    this._.push("M", this._x0 = this._x1 = +x, ",", this._y0 = this._y1 = +y);
    },
    closePath: function() {
    if (this._x1 !== null) {
    this._x1 = this._x0, this._y1 = this._y0;
    this._.push("Z");
    }
    },
    lineTo: function(x, y) {
    this._.push("L", this._x1 = +x, ",", this._y1 = +y);
    },
    quadraticCurveTo: function(x1, y1, x, y) {
    this._.push("Q", +x1, ",", +y1, ",", this._x1 = +x, ",", this._y1 = +y);
    },
    bezierCurveTo: function(x1, y1, x2, y2, x, y) {
    this._.push("C", +x1, ",", +y1, ",", +x2, ",", +y2, ",", this._x1 = +x, ",", this._y1 = +y);
    },
    arcTo: function(x1, y1, x2, y2, r) {
    x1 = +x1, y1 = +y1, x2 = +x2, y2 = +y2, r = +r;
    var x0 = this._x1,
    y0 = this._y1,
    x21 = x2 - x1,
    y21 = y2 - y1,
    x01 = x0 - x1,
    y01 = y0 - y1,
    l01_2 = x01 * x01 + y01 * y01;

    // Is the radius negative? Error.
    if (r < 0) throw new Error("negative radius: " + r);

    // Is this path empty? Move to (x1,y1).
    if (this._x1 === null) {
    this._.push(
    "M", this._x1 = x1, ",", this._y1 = y1
    );
    }

    // Or, is (x1,y1) coincident with (x0,y0)? Do nothing.
    else if (!(l01_2 > epsilon));

    // Or, are (x0,y0), (x1,y1) and (x2,y2) collinear?
    // Equivalently, is (x1,y1) coincident with (x2,y2)?
    // Or, is the radius zero? Line to (x1,y1).
    else if (!(Math.abs(y01 * x21 - y21 * x01) > epsilon) || !r) {
    this._.push(
    "L", this._x1 = x1, ",", this._y1 = y1
    );
    }

    // Otherwise, draw an arc!
    else {
    var x20 = x2 - x0,
    y20 = y2 - y0,
    l21_2 = x21 * x21 + y21 * y21,
    l20_2 = x20 * x20 + y20 * y20,
    l21 = Math.sqrt(l21_2),
    l01 = Math.sqrt(l01_2),
    l = r * Math.tan((pi - Math.acos((l21_2 + l01_2 - l20_2) / (2 * l21 * l01))) / 2),
    t01 = l / l01,
    t21 = l / l21;

    // If the start tangent is not coincident with (x0,y0), line to.
    if (Math.abs(t01 - 1) > epsilon) {
    this._.push(
    "L", x1 + t01 * x01, ",", y1 + t01 * y01
    );
    }

    this._.push(
    "A", r, ",", r, ",0,0,", +(y01 * x20 > x01 * y20), ",", this._x1 = x1 + t21 * x21, ",", this._y1 = y1 + t21 * y21
    );
    }
    },
    arc: function(x, y, r, a0, a1, ccw) {
    x = +x, y = +y, r = +r;
    var dx = r * Math.cos(a0),
    dy = r * Math.sin(a0),
    x0 = x + dx,
    y0 = y + dy,
    cw = 1 ^ ccw,
    da = ccw ? a0 - a1 : a1 - a0;

    // Is the radius negative? Error.
    if (r < 0) throw new Error("negative radius: " + r);

    // Is this path empty? Move to (x0,y0).
    if (this._x1 === null) {
    this._.push(
    "M", x0, ",", y0
    );
    }

    // Or, is (x0,y0) not coincident with the previous point? Line to (x0,y0).
    else if (Math.abs(this._x1 - x0) > epsilon || Math.abs(this._y1 - y0) > epsilon) {
    this._.push(
    "L", x0, ",", y0
    );
    }

    // Is this arc empty? We’re done.
    if (!r) return;

    // Is this a complete circle? Draw two arcs to complete the circle.
    if (da > tauEpsilon) {
    this._.push(
    "A", r, ",", r, ",0,1,", cw, ",", x - dx, ",", y - dy,
    "A", r, ",", r, ",0,1,", cw, ",", this._x1 = x0, ",", this._y1 = y0
    );
    }

    // Otherwise, draw an arc!
    else {
    if (da < 0) da = da % tau + tau;
    this._.push(
    "A", r, ",", r, ",0,", +(da >= pi), ",", cw, ",", this._x1 = x + r * Math.cos(a1), ",", this._y1 = y + r * Math.sin(a1)
    );
    }
    },
    rect: function(x, y, w, h) {
    this._.push("M", this._x0 = this._x1 = +x, ",", this._y0 = this._y1 = +y, "h", +w, "v", +h, "h", -w, "Z");
    },
    toString: function() {
    return this._.join("");
    }
    };

    var version = "0.1.2";

    exports.version = version;
    exports.path = path;

    }));
    1,610 changes: 1,610 additions & 0 deletions d3-shape.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1610 @@
    (function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-path')) :
    typeof define === 'function' && define.amd ? define('d3-shape', ['exports', 'd3-path'], factory) :
    factory((global.d3_shape = {}),global.d3_path);
    }(this, function (exports,d3Path) { 'use strict';

    function constant(x) {
    return function constant() {
    return x;
    };
    };

    var pi = Math.PI;
    var halfPi = pi / 2;
    var tau = 2 * pi;

    function arcInnerRadius(d) {
    return d.innerRadius;
    }

    function arcOuterRadius(d) {
    return d.outerRadius;
    }

    function arcStartAngle(d) {
    return d.startAngle;
    }

    function arcEndAngle(d) {
    return d.endAngle;
    }

    function arcPadAngle(d) {
    return d && d.padAngle; // Note: optional!
    }

    function asin(x) {
    return x >= 1 ? halfPi : x <= -1 ? -halfPi : Math.asin(x);
    }

    function intersect(x0, y0, x1, y1, x2, y2, x3, y3) {
    var x10 = x1 - x0, y10 = y1 - y0,
    x32 = x3 - x2, y32 = y3 - y2,
    t = (x32 * (y0 - y2) - y32 * (x0 - x2)) / (y32 * x10 - x32 * y10);
    return [x0 + t * x10, y0 + t * y10];
    }

    // Compute perpendicular offset line of length rc.
    // http://mathworld.wolfram.com/Circle-LineIntersection.html
    function cornerTangents(x0, y0, x1, y1, r1, rc, cw) {
    var x01 = x0 - x1,
    y01 = y0 - y1,
    lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01),
    ox = lo * y01,
    oy = -lo * x01,
    x11 = x0 + ox,
    y11 = y0 + oy,
    x10 = x1 + ox,
    y10 = y1 + oy,
    x00 = (x11 + x10) / 2,
    y00 = (y11 + y10) / 2,
    dx = x10 - x11,
    dy = y10 - y11,
    d2 = dx * dx + dy * dy,
    r = r1 - rc,
    D = x11 * y10 - x10 * y11,
    d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)),
    cx0 = (D * dy - dx * d) / d2,
    cy0 = (-D * dx - dy * d) / d2,
    cx1 = (D * dy + dx * d) / d2,
    cy1 = (-D * dx + dy * d) / d2,
    dx0 = cx0 - x00,
    dy0 = cy0 - y00,
    dx1 = cx1 - x00,
    dy1 = cy1 - y00;

    // Pick the closer of the two intersection points.
    // TODO Is there a faster way to determine which intersection to use?
    if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1;

    return {
    cx: cx0,
    cy: cy0,
    x01: -ox,
    y01: -oy,
    x11: cx0 * (r1 / r - 1),
    y11: cy0 * (r1 / r - 1)
    };
    }

    function arc() {
    var innerRadius = arcInnerRadius,
    outerRadius = arcOuterRadius,
    cornerRadius = constant(0),
    padRadius = null,
    startAngle = arcStartAngle,
    endAngle = arcEndAngle,
    padAngle = arcPadAngle,
    context = null,
    output = null;

    function arc() {
    var buffer,
    r,
    r0 = +innerRadius.apply(this, arguments),
    r1 = +outerRadius.apply(this, arguments),
    a0 = startAngle.apply(this, arguments) - halfPi,
    a1 = endAngle.apply(this, arguments) - halfPi,
    da = Math.abs(a1 - a0),
    cw = a1 > a0;

    if (!context) context = buffer = d3Path.path();

    // Ensure that the outer radius is always larger than the inner radius.
    if (r1 < r0) r = r1, r1 = r0, r0 = r;

    // Is it a point?
    if (!(r1 > 0)) context.moveTo(0, 0);

    // Or is it a circle or annulus?
    else if (da >= tau) {
    context.moveTo(r1 * Math.cos(a0), r1 * Math.sin(a0));
    context.arc(0, 0, r1, a0, a1, !cw);
    if (r0 > 0) {
    context.moveTo(r0 * Math.cos(a1), r0 * Math.sin(a1));
    context.arc(0, 0, r0, a1, a0, cw);
    }
    }

    // Or is it a circular or annular sector?
    else {
    var a01 = a0,
    a11 = a1,
    a00 = a0,
    a10 = a1,
    da0 = da,
    da1 = da,
    ap = padAngle.apply(this, arguments) / 2,
    rp = (ap > 0) && (padRadius ? +padRadius.apply(this, arguments) : Math.sqrt(r0 * r0 + r1 * r1)),
    rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments)),
    rc0 = rc,
    rc1 = rc;

    // Apply padding? Note that since r1 ≥ r0, da1 ≥ da0.
    if (rp > 0) {
    var p0 = asin(rp / r0 * Math.sin(ap)),
    p1 = asin(rp / r1 * Math.sin(ap));
    if ((da0 -= p0 * 2) > 0) p0 *= (cw ? 1 : -1), a00 += p0, a10 -= p0;
    else da0 = 0, a00 = a10 = (a0 + a1) / 2;
    if ((da1 -= p1 * 2) > 0) p1 *= (cw ? 1 : -1), a01 += p1, a11 -= p1;
    else da1 = 0, a01 = a11 = (a0 + a1) / 2;
    }

    var x01 = r1 * Math.cos(a01),
    y01 = r1 * Math.sin(a01),
    x10 = r0 * Math.cos(a10),
    y10 = r0 * Math.sin(a10);

    // Apply rounded corners?
    if (rc > 0) {
    var x11 = r1 * Math.cos(a11),
    y11 = r1 * Math.sin(a11),
    x00 = r0 * Math.cos(a00),
    y00 = r0 * Math.sin(a00);

    // Restrict the corner radius according to the sector angle.
    if (da < pi) {
    var oc = da0 > 0 ? intersect(x01, y01, x00, y00, x11, y11, x10, y10) : [x10, y10],
    ax = x01 - oc[0],
    ay = y01 - oc[1],
    bx = x11 - oc[0],
    by = y11 - oc[1],
    kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2),
    lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]);
    rc0 = Math.min(rc, (r0 - lc) / (kc - 1));
    rc1 = Math.min(rc, (r1 - lc) / (kc + 1));
    }
    }

    // Is the sector collapsed to a line?
    if (!(da1 > 0)) context.moveTo(x01, y01);

    // Does the sector’s outer ring have rounded corners?
    else if (rc1 > 0) {
    var t0 = cornerTangents(x00, y00, x01, y01, r1, rc1, cw),
    t1 = cornerTangents(x11, y11, x10, y10, r1, rc1, cw);

    context.moveTo(t0.cx + t0.x01, t0.cy + t0.y01);

    // Have the corners merged?
    if (rc1 < rc) context.arc(t0.cx, t0.cy, rc1, Math.atan2(t0.y01, t0.x01), Math.atan2(t1.y01, t1.x01), !cw);

    // Otherwise, draw the two corners and the ring.
    else {
    context.arc(t0.cx, t0.cy, rc1, Math.atan2(t0.y01, t0.x01), Math.atan2(t0.y11, t0.x11), !cw);
    context.arc(0, 0, r1, Math.atan2(t0.cy + t0.y11, t0.cx + t0.x11), Math.atan2(t1.cy + t1.y11, t1.cx + t1.x11), !cw);
    context.arc(t1.cx, t1.cy, rc1, Math.atan2(t1.y11, t1.x11), Math.atan2(t1.y01, t1.x01), !cw);
    }
    }

    // Or is the outer ring just a circular arc?
    else context.moveTo(x01, y01), context.arc(0, 0, r1, a01, a11, !cw);

    // Is there no inner ring, and it’s a circular sector?
    // Or perhaps it’s an annular sector collapsed due to padding?
    if (!(r0 > 0) || !(da0 > 0)) context.lineTo(x10, y10);

    // Does the sector’s inner ring (or point) have rounded corners?
    else if (rc0 > 0) {
    var t0 = cornerTangents(x10, y10, x11, y11, r0, -rc0, cw),
    t1 = cornerTangents(x01, y01, x00, y00, r0, -rc0, cw);

    context.lineTo(t0.cx + t0.x01, t0.cy + t0.y01);

    // Have the corners merged?
    if (rc0 < rc) context.arc(t0.cx, t0.cy, rc0, Math.atan2(t0.y01, t0.x01), Math.atan2(t1.y01, t1.x01), !cw);

    // Otherwise, draw the two corners and the ring.
    else {
    context.arc(t0.cx, t0.cy, rc0, Math.atan2(t0.y01, t0.x01), Math.atan2(t0.y11, t0.x11), !cw);
    context.arc(0, 0, r0, Math.atan2(t0.cy + t0.y11, t0.cx + t0.x11), Math.atan2(t1.cy + t1.y11, t1.cx + t1.x11), cw);
    context.arc(t1.cx, t1.cy, rc0, Math.atan2(t1.y11, t1.x11), Math.atan2(t1.y01, t1.x01), !cw);
    }
    }

    // Or is the inner ring just a circular arc?
    else context.arc(0, 0, r0, a10, a00, cw);
    }

    context.closePath();

    if (buffer) return context = null, buffer + "" || null;
    }

    arc.centroid = function() {
    var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2,
    a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - pi / 2;
    return [Math.cos(a) * r, Math.sin(a) * r];
    };

    arc.innerRadius = function(_) {
    return arguments.length ? (innerRadius = typeof _ === "function" ? _ : constant(+_), arc) : innerRadius;
    };

    arc.outerRadius = function(_) {
    return arguments.length ? (outerRadius = typeof _ === "function" ? _ : constant(+_), arc) : outerRadius;
    };

    arc.cornerRadius = function(_) {
    return arguments.length ? (cornerRadius = typeof _ === "function" ? _ : constant(+_), arc) : cornerRadius;
    };

    arc.padRadius = function(_) {
    return arguments.length ? (padRadius = _ == null ? null : typeof _ === "function" ? _ : constant(+_), arc) : padRadius;
    };

    arc.startAngle = function(_) {
    return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant(+_), arc) : startAngle;
    };

    arc.endAngle = function(_) {
    return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant(+_), arc) : endAngle;
    };

    arc.padAngle = function(_) {
    return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant(+_), arc) : padAngle;
    };

    arc.context = function(_) {
    return arguments.length ? ((context = output = _ == null ? null : _), arc) : context;
    };

    return arc;
    };

    var slice = Array.prototype.slice;

    function bind(curve, args) {
    if (args.length < 2) return curve;
    args = slice.call(args);
    args[0] = null;
    return function(context) {
    args[0] = context;
    return curve.apply(null, args);
    };
    };

    function Linear(context) {
    this._context = context;
    }

    Linear.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._point = 0;
    },
    lineEnd: function() {
    if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
    case 1: this._point = 2; // proceed
    default: this._context.lineTo(x, y); break;
    }
    }
    };

    function curveLinear(context) {
    return new Linear(context);
    };

    function x(p) {
    return p[0];
    };

    function y(p) {
    return p[1];
    };

    function area() {
    var x0 = x,
    x1 = null,
    y0 = constant(0),
    y1 = y,
    defined = constant(true),
    context = null,
    curve = curveLinear,
    output = null;

    function area(data) {
    var i,
    j,
    k,
    n = data.length,
    d,
    defined0 = false,
    buffer,
    x0z = new Array(n),
    y0z = new Array(n);

    if (!context) output = curve(buffer = d3Path.path());

    for (i = 0; i <= n; ++i) {
    if (!(i < n && defined(d = data[i], i, data)) === defined0) {
    if (defined0 = !defined0) {
    j = i;
    output.areaStart();
    output.lineStart();
    } else {
    output.lineEnd();
    output.lineStart();
    for (k = i - 1; k >= j; --k) {
    output.point(x0z[k], y0z[k]);
    }
    output.lineEnd();
    output.areaEnd();
    }
    }
    if (defined0) {
    x0z[i] = +x0(d, i, data), y0z[i] = +y0(d, i, data);
    output.point(x1 ? +x1(d, i, data) : x0z[i], y1 ? +y1(d, i, data) : y0z[i]);
    }
    }

    if (buffer) return output = null, buffer + "" || null;
    }

    area.x = function(_) {
    return arguments.length ? (x0 = typeof _ === "function" ? _ : constant(+_), x1 = null, area) : x0;
    };

    area.x0 = function(_) {
    return arguments.length ? (x0 = typeof _ === "function" ? _ : constant(+_), area) : x0;
    };

    area.x1 = function(_) {
    return arguments.length ? (x1 = _ == null ? null : typeof _ === "function" ? _ : constant(+_), area) : x1;
    };

    area.y = function(_) {
    return arguments.length ? (y0 = typeof _ === "function" ? _ : constant(+_), y1 = null, area) : y0;
    };

    area.y0 = function(_) {
    return arguments.length ? (y0 = typeof _ === "function" ? _ : constant(+_), area) : y0;
    };

    area.y1 = function(_) {
    return arguments.length ? (y1 = _ == null ? null : typeof _ === "function" ? _ : constant(+_), area) : y1;
    };

    area.defined = function(_) {
    return arguments.length ? (defined = typeof _ === "function" ? _ : constant(!!_), area) : defined;
    };

    area.curve = function(_) {
    return arguments.length ? (curve = bind(_, arguments), context != null && (output = curve(context)), area) : curve;
    };

    area.context = function(_) {
    return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), area) : context;
    };

    return area;
    };

    function noop() {};

    function point(that, x, y) {
    that._context.bezierCurveTo(
    (2 * that._x0 + that._x1) / 3,
    (2 * that._y0 + that._y1) / 3,
    (that._x0 + 2 * that._x1) / 3,
    (that._y0 + 2 * that._y1) / 3,
    (that._x0 + 4 * that._x1 + x) / 6,
    (that._y0 + 4 * that._y1 + y) / 6
    );
    };

    function Basis(context) {
    this._context = context;
    }

    Basis.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x0 = this._x1 =
    this._y0 = this._y1 = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    switch (this._point) {
    case 3: point(this, this._x1, this._y1); // proceed
    case 2: this._context.lineTo(this._x1, this._y1); break;
    }
    if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
    case 1: this._point = 2; break;
    case 2: this._point = 3; this._context.lineTo((5 * this._x0 + this._x1) / 6, (5 * this._y0 + this._y1) / 6); // proceed
    default: point(this, x, y); break;
    }
    this._x0 = this._x1, this._x1 = x;
    this._y0 = this._y1, this._y1 = y;
    }
    };

    function basis(context) {
    return new Basis(context);
    };

    function BasisClosed(context) {
    this._context = context;
    }

    BasisClosed.prototype = {
    areaStart: noop,
    areaEnd: noop,
    lineStart: function() {
    this._x0 = this._x1 = this._x2 = this._x3 = this._x4 =
    this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    switch (this._point) {
    case 1: {
    this._context.moveTo(this._x2, this._y2);
    this._context.closePath();
    break;
    }
    case 2: {
    this._context.moveTo((this._x2 + 2 * this._x3) / 3, (this._y2 + 2 * this._y3) / 3);
    this._context.lineTo((this._x3 + 2 * this._x2) / 3, (this._y3 + 2 * this._y2) / 3);
    this._context.closePath();
    break;
    }
    case 3: {
    this.point(this._x2, this._y2);
    this.point(this._x3, this._y3);
    this.point(this._x4, this._y4);
    break;
    }
    }
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; this._x2 = x, this._y2 = y; break;
    case 1: this._point = 2; this._x3 = x, this._y3 = y; break;
    case 2: this._point = 3; this._x4 = x, this._y4 = y; this._context.moveTo((this._x0 + 4 * this._x1 + x) / 6, (this._y0 + 4 * this._y1 + y) / 6); break;
    default: point(this, x, y); break;
    }
    this._x0 = this._x1, this._x1 = x;
    this._y0 = this._y1, this._y1 = y;
    }
    };

    function basisClosed(context) {
    return new BasisClosed(context);
    };

    function BasisOpen(context) {
    this._context = context;
    }

    BasisOpen.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x0 = this._x1 =
    this._y0 = this._y1 = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    if (this._line || (this._line !== 0 && this._point === 3)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; break;
    case 1: this._point = 2; break;
    case 2: this._point = 3; var x0 = (this._x0 + 4 * this._x1 + x) / 6, y0 = (this._y0 + 4 * this._y1 + y) / 6; this._line ? this._context.lineTo(x0, y0) : this._context.moveTo(x0, y0); break;
    case 3: this._point = 4; // proceed
    default: point(this, x, y); break;
    }
    this._x0 = this._x1, this._x1 = x;
    this._y0 = this._y1, this._y1 = y;
    }
    };

    function basisOpen(context) {
    return new BasisOpen(context);
    };

    function Bundle(context, beta) {
    this._basis = basis(context);
    this._beta = beta;
    }

    Bundle.prototype = {
    lineStart: function() {
    this._x = [];
    this._y = [];
    this._basis.lineStart();
    },
    lineEnd: function() {
    var x = this._x,
    y = this._y,
    j = x.length - 1;

    if (j > 0) {
    var x0 = x[0],
    y0 = y[0],
    dx = x[j] - x0,
    dy = y[j] - y0,
    i = -1,
    t;

    while (++i <= j) {
    t = i / j;
    this._basis.point(
    this._beta * x[i] + (1 - this._beta) * (x0 + t * dx),
    this._beta * y[i] + (1 - this._beta) * (y0 + t * dy)
    );
    }
    }

    this._x = this._y = null;
    this._basis.lineEnd();
    },
    point: function(x, y) {
    this._x.push(+x);
    this._y.push(+y);
    }
    };

    function bundle(context, beta) {
    return beta == null ? new Bundle(context, 0.85)
    : (beta = +beta) === 1 ? basis(context)
    : new Bundle(context, beta);
    };

    function point$1(that, x, y) {
    that._context.bezierCurveTo(
    that._x1 + that._k * (that._x2 - that._x0),
    that._y1 + that._k * (that._y2 - that._y0),
    that._x2 + that._k * (that._x1 - x),
    that._y2 + that._k * (that._y1 - y),
    that._x2,
    that._y2
    );
    };

    function Cardinal(context, k) {
    this._context = context;
    this._k = k;
    }

    Cardinal.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x0 = this._x1 = this._x2 =
    this._y0 = this._y1 = this._y2 = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    switch (this._point) {
    case 2: this._context.lineTo(this._x2, this._y2); break;
    case 3: point$1(this, this._x1, this._y1); break;
    }
    if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
    case 1: this._point = 2; this._x1 = x, this._y1 = y; break;
    case 2: this._point = 3; // proceed
    default: point$1(this, x, y); break;
    }
    this._x0 = this._x1, this._x1 = this._x2, this._x2 = x;
    this._y0 = this._y1, this._y1 = this._y2, this._y2 = y;
    }
    };

    function cardinal(context, tension) {
    return new Cardinal(context, (tension == null ? 1 : 1 - tension) / 6);
    };

    function CardinalClosed(context, k) {
    this._context = context;
    this._k = k;
    }

    CardinalClosed.prototype = {
    areaStart: noop,
    areaEnd: noop,
    lineStart: function() {
    this._x0 = this._x1 = this._x2 = this._x3 = this._x4 = this._x5 =
    this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = this._y5 = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    switch (this._point) {
    case 1: {
    this._context.moveTo(this._x3, this._y3);
    this._context.closePath();
    break;
    }
    case 2: {
    this._context.lineTo(this._x3, this._y3);
    this._context.closePath();
    break;
    }
    case 3: {
    this.point(this._x3, this._y3);
    this.point(this._x4, this._y4);
    this.point(this._x5, this._y5);
    break;
    }
    }
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; this._x3 = x, this._y3 = y; break;
    case 1: this._point = 2; this._context.moveTo(this._x4 = x, this._y4 = y); break;
    case 2: this._point = 3; this._x5 = x, this._y5 = y; break;
    default: point$1(this, x, y); break;
    }
    this._x0 = this._x1, this._x1 = this._x2, this._x2 = x;
    this._y0 = this._y1, this._y1 = this._y2, this._y2 = y;
    }
    };

    function cardinalClosed(context, tension) {
    return new CardinalClosed(context, (tension == null ? 1 : 1 - tension) / 6);
    };

    function CardinalOpen(context, k) {
    this._context = context;
    this._k = k;
    }

    CardinalOpen.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x0 = this._x1 = this._x2 =
    this._y0 = this._y1 = this._y2 = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    if (this._line || (this._line !== 0 && this._point === 3)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; break;
    case 1: this._point = 2; break;
    case 2: this._point = 3; this._line ? this._context.lineTo(this._x2, this._y2) : this._context.moveTo(this._x2, this._y2); break;
    case 3: this._point = 4; // proceed
    default: point$1(this, x, y); break;
    }
    this._x0 = this._x1, this._x1 = this._x2, this._x2 = x;
    this._y0 = this._y1, this._y1 = this._y2, this._y2 = y;
    }
    };

    function cardinalOpen(context, tension) {
    return new CardinalOpen(context, (tension == null ? 1 : 1 - tension) / 6);
    };

    var epsilon = 1e-6;

    function point$2(that, x, y) {
    var x1 = that._x1,
    y1 = that._y1,
    x2 = that._x2,
    y2 = that._y2;

    if (that._l01_a > epsilon) {
    var a = 2 * that._l01_2a + 3 * that._l01_a * that._l12_a + that._l12_2a,
    n = 3 * that._l01_a * (that._l01_a + that._l12_a);
    x1 = (x1 * a - that._x0 * that._l12_2a + that._x2 * that._l01_2a) / n;
    y1 = (y1 * a - that._y0 * that._l12_2a + that._y2 * that._l01_2a) / n;
    }

    if (that._l23_a > epsilon) {
    var b = 2 * that._l23_2a + 3 * that._l23_a * that._l12_a + that._l12_2a,
    m = 3 * that._l23_a * (that._l23_a + that._l12_a);
    x2 = (x2 * b + that._x1 * that._l23_2a - x * that._l12_2a) / m;
    y2 = (y2 * b + that._y1 * that._l23_2a - y * that._l12_2a) / m;
    }

    that._context.bezierCurveTo(x1, y1, x2, y2, that._x2, that._y2);
    };

    function CatmullRom(context, alpha) {
    this._context = context;
    this._alpha = alpha;
    }

    CatmullRom.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x0 = this._x1 = this._x2 =
    this._y0 = this._y1 = this._y2 = NaN;
    this._l01_a = this._l12_a = this._l23_a =
    this._l01_2a = this._l12_2a = this._l23_2a =
    this._point = 0;
    },
    lineEnd: function() {
    switch (this._point) {
    case 2: this._context.lineTo(this._x2, this._y2); break;
    case 3: this.point(this, this._x2, this._y2); break;
    }
    if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;

    if (this._point) {
    var x23 = this._x2 - x,
    y23 = this._y2 - y;
    this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha));
    }

    switch (this._point) {
    case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
    case 1: this._point = 2; break;
    case 2: this._point = 3; // proceed
    default: point$2(this, x, y); break;
    }

    this._l01_a = this._l12_a, this._l12_a = this._l23_a;
    this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a;
    this._x0 = this._x1, this._x1 = this._x2, this._x2 = x;
    this._y0 = this._y1, this._y1 = this._y2, this._y2 = y;
    }
    };

    function catmullRom(context, alpha) {
    return (alpha = alpha == null ? 0.5 : +alpha)
    ? new CatmullRom(context, alpha)
    : cardinal(context, 0);
    };

    function CatmullRomClosed(context, alpha) {
    this._context = context;
    this._alpha = alpha;
    }

    CatmullRomClosed.prototype = {
    areaStart: noop,
    areaEnd: noop,
    lineStart: function() {
    this._x0 = this._x1 = this._x2 = this._x3 = this._x4 = this._x5 =
    this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = this._y5 = NaN;
    this._l01_a = this._l12_a = this._l23_a =
    this._l01_2a = this._l12_2a = this._l23_2a =
    this._point = 0;
    },
    lineEnd: function() {
    switch (this._point) {
    case 1: {
    this._context.moveTo(this._x3, this._y3);
    this._context.closePath();
    break;
    }
    case 2: {
    this._context.lineTo(this._x3, this._y3);
    this._context.closePath();
    break;
    }
    case 3: {
    this.point(this._x3, this._y3);
    this.point(this._x4, this._y4);
    this.point(this._x5, this._y5);
    break;
    }
    }
    },
    point: function(x, y) {
    x = +x, y = +y;

    if (this._point) {
    var x23 = this._x2 - x,
    y23 = this._y2 - y;
    this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha));
    }

    switch (this._point) {
    case 0: this._point = 1; this._x3 = x, this._y3 = y; break;
    case 1: this._point = 2; this._context.moveTo(this._x4 = x, this._y4 = y); break;
    case 2: this._point = 3; this._x5 = x, this._y5 = y; break;
    default: point$2(this, x, y); break;
    }

    this._l01_a = this._l12_a, this._l12_a = this._l23_a;
    this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a;
    this._x0 = this._x1, this._x1 = this._x2, this._x2 = x;
    this._y0 = this._y1, this._y1 = this._y2, this._y2 = y;
    }
    };

    function catmullRomClosed(context, alpha) {
    return (alpha = alpha == null ? 0.5 : +alpha)
    ? new CatmullRomClosed(context, alpha)
    : cardinalClosed(context, 0);
    };

    function CatmullRomOpen(context, alpha) {
    this._context = context;
    this._alpha = alpha;
    }

    CatmullRomOpen.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x0 = this._x1 = this._x2 =
    this._y0 = this._y1 = this._y2 = NaN;
    this._l01_a = this._l12_a = this._l23_a =
    this._l01_2a = this._l12_2a = this._l23_2a =
    this._point = 0;
    },
    lineEnd: function() {
    if (this._line || (this._line !== 0 && this._point === 3)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;

    if (this._point) {
    var x23 = this._x2 - x,
    y23 = this._y2 - y;
    this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha));
    }

    switch (this._point) {
    case 0: this._point = 1; break;
    case 1: this._point = 2; break;
    case 2: this._point = 3; this._line ? this._context.lineTo(this._x2, this._y2) : this._context.moveTo(this._x2, this._y2); break;
    case 3: this._point = 4; // proceed
    default: point$2(this, x, y); break;
    }

    this._l01_a = this._l12_a, this._l12_a = this._l23_a;
    this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a;
    this._x0 = this._x1, this._x1 = this._x2, this._x2 = x;
    this._y0 = this._y1, this._y1 = this._y2, this._y2 = y;
    }
    };

    function catmullRomOpen(context, alpha) {
    return (alpha = alpha == null ? 0.5 : +alpha)
    ? new CatmullRomOpen(context, alpha)
    : cardinalOpen(context, 0);
    };

    var circle = {
    draw: function(context, size) {
    var r = Math.sqrt(size / pi);
    context.moveTo(r, 0);
    context.arc(0, 0, r, 0, tau);
    }
    };

    var cross = {
    draw: function(context, size) {
    var r = Math.sqrt(size / 5) / 2;
    context.moveTo(-3 * r, -r);
    context.lineTo(-r, -r);
    context.lineTo(-r, -3 * r);
    context.lineTo(r, -3 * r);
    context.lineTo(r, -r);
    context.lineTo(3 * r, -r);
    context.lineTo(3 * r, r);
    context.lineTo(r, r);
    context.lineTo(r, 3 * r);
    context.lineTo(-r, 3 * r);
    context.lineTo(-r, r);
    context.lineTo(-3 * r, r);
    context.closePath();
    }
    };

    var tan30 = Math.sqrt(1 / 3);
    var tan30_2 = tan30 * 2;
    var diamond = {
    draw: function(context, size) {
    var y = Math.sqrt(size / tan30_2),
    x = y * tan30;
    context.moveTo(0, -y);
    context.lineTo(x, 0);
    context.lineTo(0, y);
    context.lineTo(-x, 0);
    context.closePath();
    }
    };

    function LinearClosed(context) {
    this._context = context;
    }

    LinearClosed.prototype = {
    areaStart: noop,
    areaEnd: noop,
    lineStart: function() {
    this._point = 0;
    },
    lineEnd: function() {
    if (this._point) this._context.closePath();
    },
    point: function(x, y) {
    x = +x, y = +y;
    if (this._point) this._context.lineTo(x, y);
    else this._point = 1, this._context.moveTo(x, y);
    }
    };

    function linearClosed(context) {
    return new LinearClosed(context);
    };

    function line() {
    var x$$ = x,
    y$$ = y,
    defined = constant(true),
    context = null,
    curve = curveLinear,
    output = null;

    function line(data) {
    var i,
    n = data.length,
    d,
    defined0 = false,
    buffer;

    if (!context) output = curve(buffer = d3Path.path());

    for (i = 0; i <= n; ++i) {
    if (!(i < n && defined(d = data[i], i, data)) === defined0) {
    if (defined0 = !defined0) output.lineStart();
    else output.lineEnd();
    }
    if (defined0) output.point(+x$$(d, i, data), +y$$(d, i, data));
    }

    if (buffer) return output = null, buffer + "" || null;
    }

    line.x = function(_) {
    return arguments.length ? (x$$ = typeof _ === "function" ? _ : constant(+_), line) : x$$;
    };

    line.y = function(_) {
    return arguments.length ? (y$$ = typeof _ === "function" ? _ : constant(+_), line) : y$$;
    };

    line.defined = function(_) {
    return arguments.length ? (defined = typeof _ === "function" ? _ : constant(!!_), line) : defined;
    };

    line.curve = function(_) {
    return arguments.length ? (curve = bind(_, arguments), context != null && (output = curve(context)), line) : curve;
    };

    line.context = function(_) {
    return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), line) : context;
    };

    return line;
    };

    function sign(x) {
    return x < 0 ? -1 : 1;
    }

    // Calculate the slopes of the tangents (Hermite-type interpolation) based on
    // the following paper: Steffen, M. 1990. A Simple Method for Monotonic
    // Interpolation in One Dimension. Astronomy and Astrophysics, Vol. 239, NO.
    // NOV(II), P. 443, 1990.
    function slope3(that, x2, y2) {
    var h0 = that._x1 - that._x0,
    h1 = x2 - that._x1,
    s0 = (that._y1 - that._y0) / h0,
    s1 = (y2 - that._y1) / h1,
    p = (s0 * h1 + s1 * h0) / (h0 + h1);
    return (sign(s0) + sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), 0.5 * Math.abs(p)) || 0;
    }

    // Calculate a one-sided slope.
    function slope2(that, t) {
    var h = that._x1 - that._x0;
    return h ? (3 * (that._y1 - that._y0) / h - t) / 2 : t;
    }

    // According to https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Representations
    // "you can express cubic Hermite interpolation in terms of cubic Bézier curves
    // with respect to the four values p0, p0 + m0 / 3, p1 - m1 / 3, p1".
    function point$3(that, t0, t1) {
    var x0 = that._x0,
    y0 = that._y0,
    x1 = that._x1,
    y1 = that._y1,
    dx = (x1 - x0) / 3;
    that._context.bezierCurveTo(x0 + dx, y0 + dx * t0, x1 - dx, y1 - dx * t1, x1, y1);
    }

    function Monotone(context) {
    this._context = context;
    }

    Monotone.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x0 = this._x1 =
    this._y0 = this._y1 =
    this._t0 = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    switch (this._point) {
    case 2: this._context.lineTo(this._x1, this._y1); break;
    case 3: point$3(this, this._t0, slope2(this, this._t0)); break;
    }
    if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    var t1 = NaN;

    x = +x, y = +y;
    if (x === this._x1 && y === this._y1) return; // Ignore coincident points.
    switch (this._point) {
    case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
    case 1: this._point = 2; break;
    case 2: this._point = 3; point$3(this, slope2(this, t1 = slope3(this, x, y)), t1); break;
    default: point$3(this, this._t0, t1 = slope3(this, x, y)); break;
    }

    this._x0 = this._x1, this._x1 = x;
    this._y0 = this._y1, this._y1 = y;
    this._t0 = t1;
    }
    }

    function monotone(context) {
    return new Monotone(context);
    };

    function Natural(context) {
    this._context = context;
    }

    Natural.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x = [];
    this._y = [];
    },
    lineEnd: function() {
    var x = this._x,
    y = this._y,
    n = x.length;

    if (n) {
    this._line ? this._context.lineTo(x[0], y[0]) : this._context.moveTo(x[0], y[0]);
    if (n === 2) {
    this._context.lineTo(x[1], y[1]);
    } else {
    var px = controlPoints(x),
    py = controlPoints(y);
    for (var i0 = 0, i1 = 1; i1 < n; ++i0, ++i1) {
    this._context.bezierCurveTo(px[0][i0], py[0][i0], px[1][i0], py[1][i0], x[i1], y[i1]);
    }
    }
    }

    if (this._line || (this._line !== 0 && n === 1)) this._context.closePath();
    this._line = 1 - this._line;
    this._x = this._y = null;
    },
    point: function(x, y) {
    this._x.push(+x);
    this._y.push(+y);
    }
    };

    // See https://www.particleincell.com/2012/bezier-splines/ for derivation.
    function controlPoints(x) {
    var i,
    n = x.length - 1,
    m,
    a = new Array(n),
    b = new Array(n),
    r = new Array(n);
    a[0] = 0, b[0] = 2, r[0] = x[0] + 2 * x[1];
    for (i = 1; i < n - 1; ++i) a[i] = 1, b[i] = 4, r[i] = 4 * x[i] + 2 * x[i + 1];
    a[n - 1] = 2, b[n - 1] = 7, r[n - 1] = 8 * x[n - 1] + x[n];
    for (i = 1; i < n; ++i) m = a[i] / b[i - 1], b[i] -= m, r[i] -= m * r[i - 1];
    a[n - 1] = r[n - 1] / b[n - 1];
    for (i = n - 2; i >= 0; --i) a[i] = (r[i] - a[i + 1]) / b[i];
    b[n - 1] = (x[n] + a[n - 1]) / 2;
    for (i = 0; i < n - 1; ++i) b[i] = 2 * x[i + 1] - a[i + 1];
    return [a, b];
    }

    function natural(context) {
    return new Natural(context);
    };

    function descending(a, b) {
    return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
    };

    function identity(d) {
    return d;
    };

    function pie() {
    var value = identity,
    sortValues = descending,
    sort = null,
    startAngle = constant(0),
    endAngle = constant(tau),
    padAngle = constant(0);

    function pie(data) {
    var n = data.length,
    sum = 0,
    index = new Array(n),
    arcs = new Array(n),
    a0 = +startAngle.apply(this, arguments),
    da = Math.min(tau, Math.max(-tau, endAngle.apply(this, arguments) - a0)),
    a1,
    p = Math.min(Math.abs(da) / n, padAngle.apply(this, arguments)),
    pa = p * (da < 0 ? -1 : 1);

    for (var i = 0, v; i < n; ++i) {
    if ((v = arcs[index[i] = i] = +value(data[i], i, data)) > 0) {
    sum += v;
    }
    }

    // Optionally sort the arcs by previously-computed values or by data.
    if (sortValues != null) index.sort(function(i, j) { return sortValues(arcs[i], arcs[j]); });
    else if (sort !== null) index.sort(function(i, j) { return sort(data[i], data[j]); });

    // Compute the arcs! They are stored in the original data's order.
    for (var i = 0, j, k = sum ? (da - n * pa) / sum : 0; i < n; ++i, a0 = a1) {
    j = index[i], v = arcs[j], a1 = a0 + (v > 0 ? v * k : 0) + pa, arcs[j] = {
    data: data[j],
    value: v,
    startAngle: a0,
    endAngle: a1,
    padAngle: p
    };
    }

    return arcs;
    }

    pie.value = function(_) {
    return arguments.length ? (value = typeof _ === "function" ? _ : constant(+_), pie) : value;
    };

    pie.sortValues = function(_) {
    return arguments.length ? (sortValues = _, sort = null, pie) : sortValues;
    };

    pie.sort = function(_) {
    return arguments.length ? (sort = _, sortValues = null, pie) : sort;
    };

    pie.startAngle = function(_) {
    return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant(+_), pie) : startAngle;
    };

    pie.endAngle = function(_) {
    return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant(+_), pie) : endAngle;
    };

    pie.padAngle = function(_) {
    return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant(+_), pie) : padAngle;
    };

    return pie;
    };

    function Radial(curve) {
    this._curve = curve;
    }

    Radial.prototype = {
    areaStart: function() {
    this._curve.areaStart();
    },
    areaEnd: function() {
    this._curve.areaEnd();
    },
    lineStart: function() {
    this._curve.lineStart();
    },
    lineEnd: function() {
    this._curve.lineEnd();
    },
    point: function(a, r) {
    a -= halfPi, this._curve.point(r * Math.cos(a), r * Math.sin(a));
    }
    };

    function curveRadial(curve, args) {
    curve = bind(curve, args);

    function radial(context) {
    return new Radial(curve(context));
    }

    radial._curve = curve;

    return radial;
    };

    function radialArea() {
    var a = area(),
    c = a.curve;

    a.angle = a.x, delete a.x;
    a.startAngle = a.x0, delete a.x0;
    a.endAngle = a.x1, delete a.x1;
    a.radius = a.y, delete a.y;
    a.innerRadius = a.y0, delete a.y0;
    a.outerRadius = a.y1, delete a.y1;

    a.curve = function(_) {
    return arguments.length ? c(curveRadial(_, arguments)) : c()._curve;
    };

    return a.curve(curveLinear);
    };

    function radialLine() {
    var l = line(),
    c = l.curve;

    l.angle = l.x, delete l.x;
    l.radius = l.y, delete l.y;

    l.curve = function(_) {
    return arguments.length ? c(curveRadial(_, arguments)) : c()._curve;
    };

    return l.curve(curveLinear);
    };

    var square = {
    draw: function(context, size) {
    var w = Math.sqrt(size),
    x = -w / 2;
    context.rect(x, x, w, w);
    }
    };

    var ka = 0.89081309152928522810;
    var kr = Math.sin(pi / 10) / Math.sin(7 * pi / 10);
    var kx = Math.sin(tau / 10) * kr;
    var ky = -Math.cos(tau / 10) * kr;
    var star = {
    draw: function(context, size) {
    var r = Math.sqrt(size * ka),
    x = kx * r,
    y = ky * r;
    context.moveTo(0, -r);
    context.lineTo(x, y);
    for (var i = 1; i < 5; ++i) {
    var a = tau * i / 5,
    c = Math.cos(a),
    s = Math.sin(a);
    context.lineTo(s * r, -c * r);
    context.lineTo(c * x - s * y, s * x + c * y);
    }
    context.closePath();
    }
    };

    function StepAfter(context) {
    this._context = context;
    }

    StepAfter.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._y = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
    case 1: this._point = 2; // proceed
    default: {
    this._context.lineTo(x, this._y);
    this._context.lineTo(x, y);
    break;
    }
    }
    this._y = y;
    }
    };

    function stepAfter(context) {
    return new StepAfter(context);
    };

    function StepBefore(context) {
    this._context = context;
    }

    StepBefore.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
    case 1: this._point = 2; // proceed
    default: {
    this._context.lineTo(this._x, y);
    this._context.lineTo(x, y);
    break;
    }
    }
    this._x = x;
    }
    };

    function stepBefore(context) {
    return new StepBefore(context);
    };

    function Step(context) {
    this._context = context;
    }

    Step.prototype = {
    areaStart: function() {
    this._line = 0;
    },
    areaEnd: function() {
    this._line = NaN;
    },
    lineStart: function() {
    this._x = this._y = NaN;
    this._point = 0;
    },
    lineEnd: function() {
    if (this._point === 2) this._context.lineTo(this._x, this._y);
    if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
    this._line = 1 - this._line;
    },
    point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
    case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
    case 1: this._point = 2; // proceed
    default: {
    var x1 = (this._x + x) / 2;
    this._context.lineTo(x1, this._y);
    this._context.lineTo(x1, y);
    break;
    }
    }
    this._x = x, this._y = y;
    }
    };

    function step(context) {
    return new Step(context);
    };

    var c = -0.5;
    var s = Math.sqrt(3) / 2;
    var k = 1 / Math.sqrt(12);
    var a = (k / 2 + 1) * 3;
    var wye = {
    draw: function(context, size) {
    var r = Math.sqrt(size / a),
    x0 = r / 2,
    y0 = r * k,
    x1 = x0,
    y1 = r * k + r,
    x2 = -x1,
    y2 = y1;
    context.moveTo(x0, y0);
    context.lineTo(x1, y1);
    context.lineTo(x2, y2);
    context.lineTo(c * x0 - s * y0, s * x0 + c * y0);
    context.lineTo(c * x1 - s * y1, s * x1 + c * y1);
    context.lineTo(c * x2 - s * y2, s * x2 + c * y2);
    context.lineTo(c * x0 + s * y0, c * y0 - s * x0);
    context.lineTo(c * x1 + s * y1, c * y1 - s * x1);
    context.lineTo(c * x2 + s * y2, c * y2 - s * x2);
    context.closePath();
    }
    };

    var sqrt3 = Math.sqrt(3);

    var triangle = {
    draw: function(context, size) {
    var y = -Math.sqrt(size / (sqrt3 * 3));
    context.moveTo(0, y * 2);
    context.lineTo(-sqrt3 * y, -y);
    context.lineTo(sqrt3 * y, -y);
    context.closePath();
    }
    };

    var symbols = [
    circle,
    cross,
    diamond,
    square,
    star,
    triangle,
    wye
    ];

    function symbol() {
    var type = constant(circle),
    size = constant(64),
    context = null;

    function symbol() {
    var buffer;
    if (!context) context = buffer = d3Path.path();
    type.apply(this, arguments).draw(context, +size.apply(this, arguments));
    if (buffer) return context = null, buffer + "" || null;
    }

    symbol.type = function(_) {
    return arguments.length ? (type = typeof _ === "function" ? _ : constant(_), symbol) : type;
    };

    symbol.size = function(_) {
    return arguments.length ? (size = typeof _ === "function" ? _ : constant(+_), symbol) : size;
    };

    symbol.context = function(_) {
    return arguments.length ? (context = _ == null ? null : _, symbol) : context;
    };

    return symbol;
    };

    var version = "0.2.0";

    exports.version = version;
    exports.arc = arc;
    exports.area = area;
    exports.basisClosed = basisClosed;
    exports.basisOpen = basisOpen;
    exports.basis = basis;
    exports.bundle = bundle;
    exports.cardinalClosed = cardinalClosed;
    exports.cardinalOpen = cardinalOpen;
    exports.cardinal = cardinal;
    exports.catmullRomClosed = catmullRomClosed;
    exports.catmullRomOpen = catmullRomOpen;
    exports.catmullRom = catmullRom;
    exports.circle = circle;
    exports.cross = cross;
    exports.diamond = diamond;
    exports.linearClosed = linearClosed;
    exports.linear = curveLinear;
    exports.line = line;
    exports.monotone = monotone;
    exports.natural = natural;
    exports.pie = pie;
    exports.radialArea = radialArea;
    exports.radialLine = radialLine;
    exports.square = square;
    exports.star = star;
    exports.stepAfter = stepAfter;
    exports.stepBefore = stepBefore;
    exports.step = step;
    exports.symbol = symbol;
    exports.symbols = symbols;
    exports.triangle = triangle;
    exports.wye = wye;

    }));
    96 changes: 96 additions & 0 deletions index.coffee
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,96 @@
    MARGIN = 20
    MARGIN_LEFT = 20
    MARGIN_BOTTOM = 40
    keyword = 'cocaine'

    # FIXME se la API li cambiano, devono restituire i nuovi valori. Se non lo dicono il valore di end dev'essere una tacca più avanti, altrimenti manca l'ultimo valore.
    START = 915148800
    END = 1450310400

    #FIXME lo step non può essere fisso in secondi, ci vuole un'aggregazione per mesi o settimane veri
    STEP = 604800 # 1 week
    #STEP = 2592000 # 30 days

    svg = d3.select('svg')
    width = svg.node().getBoundingClientRect().width
    height = svg.node().getBoundingClientRect().height

    chart_width = width-2*MARGIN-MARGIN_LEFT
    chart_height = height-2*MARGIN-MARGIN_BOTTOM

    chart = svg.append 'g'
    .attr
    transform: "translate(#{MARGIN+MARGIN_LEFT} #{MARGIN})"

    x = d3.time.scale()
    .range([0, chart_width])

    y = d3.scale.linear()
    .range([chart_height, 0])


    line_generator = d3_shape.area()
    .x (d) -> x(d.date)
    .y1 (d) -> y(d.value)
    .y0 (d) -> y(0)
    .curve d3_shape.stepAfter # step interpolator is the only one that can be applied after wrapping and wrap without error

    redraw = () ->
    d3.json "http://wafi.iit.cnr.it/cas-scraper2/api/timeseries/getTermTs.php?db=drugsforum&term=#{keyword}&start=#{START}&end=#{END}&step=#{STEP}", (result) ->
    start = new Date(d3.min(result.data, (d) -> d.date)*1000)
    end = new Date(d3.max(result.data, (d) -> d.date)*1000+(STEP*1000))
    result.data.forEach (d) ->
    d.date = new Date(+d.date*1000)

    x
    .domain([start, end])

    y
    .domain([0, d3.max(result.data, (d) -> d.value)])

    # add a fake data point to extend the stepped curve to include the last value
    result.data.push {date: end, value: null}

    # define the x axis
    xAxis = d3.svg.axis()
    .orient("bottom")
    .ticks(d3.time.year, 1) # FIXME non si sa se d3 posiziona i tick secondo l'ora locale o secondo l'ora UTC. E se scrive considerando l'ora giusta o no.
    .scale(x)

    # define the y axis
    yAxis = d3.svg.axis()
    .orient("left")
    # .ticks(10)
    .scale(y)

    chart.selectAll('*')
    .remove()


    # draw x axis with labels and move to the bottom of the chart area
    chart.append("g")
    .attr "class", "xaxis"
    .attr "transform", "translate(0,#{chart_height+0.5})"
    .call(xAxis)

    # draw y axis with labels and move to the bottom of the chart area
    chart.append("g")
    .attr "class", "yaxis"
    .call(yAxis)

    chart.append('path')
    .datum result.data
    .attr
    class: 'area'
    d: line_generator
    fill: 'orange'

    d3.select("#keyword").node().value = keyword

    d3.select("#keyword").on 'keyup', () ->
    if(d3.event.keyCode == 13)
    keyword = this.value
    keyword = keyword.replace(" ", "%2B")
    redraw()

    redraw()
    29 changes: 29 additions & 0 deletions index.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,29 @@
    body, html {
    padding: 0;
    margin: 0;
    width: 100%;
    height: 100%;
    font-family: sans-serif;
    font-size: 12px;
    }
    svg {
    width: 100%;
    height: 100%;
    background: white;
    }
    .tick line {
    stroke: black;
    stroke-width: 1px;
    }
    .xaxis path, .axis line, .yaxis path {
    stroke-width:1px;
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
    }

    input{
    padding : 0 2px;
    margin : 0;
    width : 240px;
    }
    19 changes: 19 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <title>Term frequencies (Cassandra)</title>
    <link rel="stylesheet" href="index.css">
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script src="d3-path.js"></script>
    <script src="d3-shape.js"></script>
    </head>
    <body>
    <div id="search">
    <label>Keyword:</label>
    <input type="search" id="keyword">
    </div>
    <svg></svg>
    <script src="index.js"></script>
    </body>
    </html>
    92 changes: 92 additions & 0 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,92 @@
    // Generated by CoffeeScript 1.10.0
    (function() {
    var END, MARGIN, MARGIN_BOTTOM, MARGIN_LEFT, START, STEP, chart, chart_height, chart_width, height, keyword, line_generator, redraw, svg, width, x, y;

    MARGIN = 20;

    MARGIN_LEFT = 20;

    MARGIN_BOTTOM = 40;

    keyword = 'cocaine';

    START = 915148800;

    END = 1450310400;

    STEP = 604800;

    svg = d3.select('svg');

    width = svg.node().getBoundingClientRect().width;

    height = svg.node().getBoundingClientRect().height;

    chart_width = width - 2 * MARGIN - MARGIN_LEFT;

    chart_height = height - 2 * MARGIN - MARGIN_BOTTOM;

    chart = svg.append('g').attr({
    transform: "translate(" + (MARGIN + MARGIN_LEFT) + " " + MARGIN + ")"
    });

    x = d3.time.scale().range([0, chart_width]);

    y = d3.scale.linear().range([chart_height, 0]);

    line_generator = d3_shape.area().x(function(d) {
    return x(d.date);
    }).y1(function(d) {
    return y(d.value);
    }).y0(function(d) {
    return y(0);
    }).curve(d3_shape.stepAfter);

    redraw = function() {
    return d3.json("http://wafi.iit.cnr.it/cas-scraper2/api/timeseries/getTermTs.php?db=drugsforum&term=" + keyword + "&start=" + START + "&end=" + END + "&step=" + STEP, function(result) {
    var end, start, xAxis, yAxis;
    start = new Date(d3.min(result.data, function(d) {
    return d.date;
    }) * 1000);
    end = new Date(d3.max(result.data, function(d) {
    return d.date;
    }) * 1000 + (STEP * 1000));
    result.data.forEach(function(d) {
    return d.date = new Date(+d.date * 1000);
    });
    x.domain([start, end]);
    y.domain([
    0, d3.max(result.data, function(d) {
    return d.value;
    })
    ]);
    result.data.push({
    date: end,
    value: null
    });
    xAxis = d3.svg.axis().orient("bottom").ticks(d3.time.year, 1).scale(x);
    yAxis = d3.svg.axis().orient("left").scale(y);
    chart.selectAll('*').remove();
    chart.append("g").attr("class", "xaxis").attr("transform", "translate(0," + (chart_height + 0.5) + ")").call(xAxis);
    chart.append("g").attr("class", "yaxis").call(yAxis);
    return chart.append('path').datum(result.data).attr({
    "class": 'area',
    d: line_generator,
    fill: 'orange'
    });
    });
    };

    d3.select("#keyword").node().value = keyword;

    d3.select("#keyword").on('keyup', function() {
    if (d3.event.keyCode === 13) {
    keyword = this.value;
    keyword = keyword.replace(" ", "%2B");
    return redraw();
    }
    });

    redraw();

    }).call(this);
    80 changes: 80 additions & 0 deletions temp.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,80 @@
    {
    "data": [
    {
    "date": 1042156800,
    "value": 0
    },
    {
    "date": 1044748800,
    "value": 50
    },
    {
    "date": 1047340800,
    "value": 100
    },
    {
    "date": 1049932800,
    "value": 150
    },
    {
    "date": 1052524800,
    "value": 200
    },
    {
    "date": 1055116800,
    "value": 250
    },
    {
    "date": 1057708800,
    "value": 300
    },
    {
    "date": 1060300800,
    "value": 350
    },
    {
    "date": 1062892800,
    "value": 375
    },
    {
    "date": 1065484800,
    "value": 425
    },
    {
    "date": 1068076800,
    "value": 1000
    },
    {
    "date": 1070668800,
    "value": 1025
    },
    {
    "date": 1073260800,
    "value": 750
    },
    {
    "date": 1075852800,
    "value": 0
    },
    {
    "date": 1078444800,
    "value": 50
    },
    {
    "date": 1081036800,
    "value": 675
    },
    {
    "date": 1083628800,
    "value": 50
    },
    {
    "date": 1086220800,
    "value": 150
    },
    {
    "date": 1088812800,
    "value": 50
    }],
    "tot": 896311
    }
    Binary file added thumbnail.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  3. nitaku created this gist Dec 21, 2015.
    1 change: 1 addition & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    -