diff --git a/kitsune/community/jinja2/community/metrics.html b/kitsune/community/jinja2/community/metrics.html index c76f7864959..fd0b1681440 100644 --- a/kitsune/community/jinja2/community/metrics.html +++ b/kitsune/community/jinja2/community/metrics.html @@ -31,13 +31,7 @@
>
+ const out = new Map();
+ for (const r of results) {
+ if (!out.has(r.code)) out.set(r.code, new Map());
+ out.get(r.code).set(r.date, r.value);
+ }
+ return out;
+}
+
+function replaceWithCanvas(section, height = 320) {
+ const old = section.querySelector(".rickshaw");
+ if (old) old.remove();
+ const wrap = document.createElement("div");
+ wrap.style.cssText = `position: relative; height: ${height}px; width: 100%; margin-bottom: 16px;`;
+ const canvas = document.createElement("canvas");
+ wrap.appendChild(canvas);
+ section.appendChild(wrap);
+ return canvas;
+}
+
+function renderLocaleLocalizationChart(section, byCodeAndDate) {
+ const canvas = replaceWithCanvas(section);
+ const codes = [
+ { code: "percent_localized_top100", label: gettext("Top 100 Articles"), color: "#5d84b2" },
+ { code: "percent_localized_top20", label: gettext("Top 20 Articles"), color: "#aa4643" },
+ { code: "percent_localized_all", label: gettext("All Articles"), color: "#89a54e" },
+ ];
+
+ const allDates = new Set();
+ for (const { code } of codes) {
+ const byDate = byCodeAndDate.get(code);
+ if (byDate) for (const d of byDate.keys()) allDates.add(d);
+ }
+ const sortedDates = [...allDates].sort();
+ const dateObjs = sortedDates.map(parseISO);
+
+ const datasets = codes.map(({ code, label, color }) => {
+ const byDate = byCodeAndDate.get(code) || new Map();
+ return {
+ label,
+ borderColor: color,
+ backgroundColor: color,
+ pointRadius: 0,
+ borderWidth: 1.5,
+ fill: false,
+ data: sortedDates.map((d, i) => ({ x: dateObjs[i], y: byDate.has(d) ? byDate.get(d) : null })),
+ };
+ });
+
+ renderLineChart(canvas, lineChartOptions(datasets, {
+ yTitle: gettext("% Localized"),
+ max: 100,
+ valueFormatter: (v) => `${v.toFixed(1)}%`,
+ }));
+}
+
+function renderLocaleContributorsChart(section, byCodeAndDate) {
+ const canvas = replaceWithCanvas(section);
+ const byDate = byCodeAndDate.get("active_contributors") || new Map();
+ const sorted = [...byDate.keys()].sort();
+ const dataset = {
+ label: gettext("Active Contributors"),
+ borderColor: "#5d84b2",
+ backgroundColor: "rgba(93,132,178,0.15)",
+ pointRadius: 0,
+ borderWidth: 1.5,
+ fill: true,
+ data: sorted.map(d => ({ x: parseISO(d), y: byDate.get(d) })),
+ };
+ renderLineChart(canvas, lineChartOptions([dataset], {
+ yTitle: gettext("Contributors"),
+ max: null,
+ valueFormatter: (v) => v.toLocaleString(),
+ }));
+}
+
+function renderHelpfulVotesChart(section, objects) {
+ const canvas = replaceWithCanvas(section);
+ const sorted = [...objects].sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
+ const dataset = {
+ label: gettext("Article Votes: % Helpful"),
+ borderColor: "#aa4643",
+ backgroundColor: "rgba(170,70,67,0.15)",
+ pointRadius: 0,
+ borderWidth: 1.5,
+ fill: true,
+ data: sorted.map(o => ({
+ x: parseISO(o.date),
+ y: o.kb_votes > 0 ? (100 * o.kb_helpful) / o.kb_votes : null,
+ })),
+ };
+ renderLineChart(canvas, lineChartOptions([dataset], {
+ yTitle: gettext("% Helpful"),
+ max: 100,
+ valueFormatter: (v) => `${v.toFixed(1)}%`,
+ }));
+}
+
+function lineChartOptions(datasets, { yTitle, max, valueFormatter }) {
+ return {
+ type: "line",
+ data: { datasets },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ x: {
+ type: "time",
+ time: {
+ tooltipFormat: "PP",
+ displayFormats: { day: "MMM d", week: "MMM d", month: "MMM yyyy", quarter: "MMM yyyy", year: "yyyy" },
+ },
+ },
+ y: {
+ beginAtZero: true,
+ max,
+ title: { display: true, text: yTitle },
+ },
+ },
+ plugins: {
+ legend: { position: "bottom" },
+ tooltip: {
+ mode: "index",
+ callbacks: {
+ label: (ctx) => {
+ const v = ctx.parsed.y;
+ if (v == null) return null;
+ return `${ctx.dataset.label}: ${valueFormatter(v)}`;
+ },
+ },
+ },
+ },
+ },
+ };
+}
+
+async function fetchAllPages(url, maxPages) {
+ const results = [];
+ let next = url;
+ let count = 0;
+ while (next && count < maxPages) {
+ const response = await fetch(next);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ const data = await response.json();
+ results.push(...(data.results || []));
+ next = data.next;
+ count++;
+ }
+ return results;
+}
+
+function groupResultsByCode(results) {
+ // Returns Map>>
+ const out = new Map();
+ for (const r of results) {
+ if (!out.has(r.code)) out.set(r.code, new Map());
+ const byDate = out.get(r.code);
+ if (!byDate.has(r.date)) byDate.set(r.date, new Map());
+ byDate.get(r.date).set(r.locale, r.value);
+ }
+ return out;
+}
+
+function renderMultiLocaleChart(section, dataForCode, allLocales, visibleLocales, cfg) {
+ const oldRickshaw = section.querySelector(".rickshaw");
+ if (oldRickshaw) oldRickshaw.remove();
+
+ const wrap = document.createElement("div");
+ wrap.style.cssText = "position: relative; height: 320px; width: 100%; margin-bottom: 16px;";
+ const canvas = document.createElement("canvas");
+ wrap.appendChild(canvas);
+ section.appendChild(wrap);
+
+ const dates = Array.from(dataForCode.keys()).sort();
+ const dateObjs = dates.map((d) => parseISO(d));
+ const datasets = allLocales.map((locale, i) => ({
+ label: locale,
+ borderColor: COLORS[i % COLORS.length],
+ backgroundColor: COLORS[i % COLORS.length],
+ pointRadius: 0,
+ borderWidth: 1.2,
+ fill: false,
+ hidden: !visibleLocales.has(locale),
+ data: dates.map((d, idx) => ({ x: dateObjs[idx], y: dataForCode.get(d).get(locale) ?? null })),
+ }));
+
+ return renderLineChart(canvas, {
+ type: "line",
+ data: { datasets },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ x: {
+ type: "time",
+ time: {
+ tooltipFormat: "PP",
+ displayFormats: { day: "MMM d", week: "MMM d", month: "MMM yyyy", quarter: "MMM yyyy", year: "yyyy" },
+ },
+ },
+ y: {
+ beginAtZero: true,
+ max: cfg.max,
+ title: { display: true, text: cfg.yLabel() },
+ },
+ },
+ plugins: {
+ legend: {
+ position: "bottom",
+ display: false,
+ },
+ tooltip: {
+ mode: "nearest",
+ intersect: false,
+ callbacks: {
+ label: (ctx) => {
+ const v = ctx.parsed.y;
+ if (v == null) return null;
+ const formatted = cfg.max === 100 ? `${v.toFixed(1)}%` : v.toLocaleString();
+ return `${ctx.dataset.label}: ${formatted}`;
+ },
+ },
+ },
+ },
+ },
+ });
+}
+
+function updateVisibleLocales(charts) {
+ const checkedLocales = new Set(
+ Array.from(document.querySelectorAll("#locale-picker input[type=checkbox]:checked")).map(
+ (cb) => cb.value,
+ ),
+ );
+ for (const { chart } of charts) {
+ chart.data.datasets.forEach((ds) => {
+ ds.hidden = !checkedLocales.has(ds.label);
+ });
+ chart.update();
+ }
+}
diff --git a/kitsune/sumo/static/sumo/js/historycharts.js b/kitsune/sumo/static/sumo/js/historycharts.js
deleted file mode 100644
index 65b9fa27b73..00000000000
--- a/kitsune/sumo/static/sumo/js/historycharts.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import { Graph } from "sumo/js/rickshaw_utils";
-
-/*
-* Scripts to support Graphs on wiki article history.
-*/
-
-(function ($) {
- function init() {
- $('#show-graph').off('click');
- $('#show-graph').html(gettext('Loading...'));
- $('#show-graph').css('color', '#333333').css('cursor', 'auto').css('text-decoration', 'none');
- initGraph();
- }
-
- function initGraph() {
- $.ajax({
- type: 'GET',
- url: $('#helpful-graph').data('url'),
- success: function (data) {
- if (data.datums.length > 0) {
- rickshawGraph(data);
- $('#show-graph').hide();
- } else {
- $('#show-graph').html(gettext('No votes data'));
- $('#show-graph').off('click');
- }
- },
- error: function () {
- $('#show-graph').html(gettext('Error loading graph'));
- $('#show-graph').off('click');
- }
- });
- }
-
- function rickshawGraph(data) {
- var $container = $('#helpful-graph');
- var sets = {};
-
- sets[gettext('Votes')] = ['yes', 'no'];
- sets[gettext('Percent')] = ['percent'];
-
- data.seriesSpec = [
- {
- name: gettext('Yes'),
- slug: 'yes',
- func: Graph.identity('yes'),
- color: '#21de2b',
- axisGroup: 'votes'
- },
- {
- name: gettext('No'),
- slug: 'no',
- func: Graph.identity('no'),
- color: '#de2b21',
- axisGroup: 'votes'
- },
- {
- name: gettext('Percent'),
- slug: 'percent',
- func: Graph.percentage('yes', 'no'),
- color: '#2b21de',
- axisGroup: 'percent',
- type: 'percent'
- }
- ];
-
- $container.show();
- var graph = new Graph($container, {
- data: data,
- graph: {
- width: 600
- },
- options: {
- bucket: true,
- legend: false,
- sets: true,
- timeline: true
- },
- metadata: {
- sets: sets
- }
- });
-
- graph.render();
- }
-
- $('#show-graph').on("click", init);
-})(jQuery);
diff --git a/kitsune/sumo/static/sumo/js/libs/d3.layout.min.js b/kitsune/sumo/static/sumo/js/libs/d3.layout.min.js
deleted file mode 100644
index 1da2530cdd6..00000000000
--- a/kitsune/sumo/static/sumo/js/libs/d3.layout.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import d3 from "d3";
-(function(){function a(a){var b=a.source,d=a.target,e=c(b,d),f=[b];while(b!==e)b=b.parent,f.push(b);var g=f.length;while(d!==e)f.splice(g,0,d),d=d.parent;return f}function b(a){var b=[],c=a.parent;while(c!=null)b.push(a),a=c,c=c.parent;return b.push(a),b}function c(a,c){if(a===c)return a;var d=b(a),e=b(c),f=d.pop(),g=e.pop(),h=null;while(f===g)h=f,f=d.pop(),g=e.pop();return h}function g(a){a.fixed|=2}function h(a){a!==f&&(a.fixed&=1)}function i(){j(),f.fixed&=1,e=f=null}function j(){f.px+=d3.event.dx,f.py+=d3.event.dy,e.resume()}function k(a,b,c){var d=0,e=0;a.charge=0;if(!a.leaf){var f=a.nodes,g=f.length,h=-1,i;while(++hd&&(c=b,d=e);return c}function u(a){return a.reduce(v,0)}function v(a,b){return a+b[1]}function w(a,b){return x(a,Math.ceil(Math.log(b.length)/Math.LN2+1))}function x(a,b){var c=-1,d=+a[0],e=(a[1]-d)/b,f=[];while(++c<=b)f[c]=e*c+d;return f}function y(a){return[d3.min(a),d3.max(a)]}function z(a,b){return a.sort=d3.rebind(a,b.sort),a.children=d3.rebind(a,b.children),a.links=D,a.value=d3.rebind(a,b.value),a.nodes=function(b){return E=!0,(a.nodes=a)(b)},a}function A(a){return a.children}function B(a){return a.value}function C(a,b){return b.value-a.value}function D(a){return d3.merge(a.map(function(a){return(a.children||[]).map(function(b){return{source:a,target:b}})}))}function F(a,b){return a.value-b.value}function G(a,b){var c=a._pack_next;a._pack_next=b,b._pack_prev=a,b._pack_next=c,c._pack_prev=b}function H(a,b){a._pack_next=b,b._pack_prev=a}function I(a,b){var c=b.x-a.x,d=b.y-a.y,e=a.r+b.r;return e*e-c*c-d*d>.001}function J(a){function l(a){b=Math.min(a.x-a.r,b),c=Math.max(a.x+a.r,c),d=Math.min(a.y-a.r,d),e=Math.max(a.y+a.r,e)}var b=Infinity,c=-Infinity,d=Infinity,e=-Infinity,f=a.length,g,h,i,j,k;a.forEach(K),g=a[0],g.x=-g.r,g.y=0,l(g);if(f>1){h=a[1],h.x=h.r,h.y=0,l(h);if(f>2){i=a[2],O(g,h,i),l(i),G(g,i),g._pack_prev=i,G(i,h),h=g._pack_next;for(var m=3;m0?(H(g,j),h=j,m--):(H(j,h),g=j,m--)}}}var q=(b+c)/2,r=(d+e)/2,s=0;for(var m=0;m0&&(a=d)}return a}function X(a,b){return a.x-b.x}function Y(a,b){return b.x-a.x}function Z(a,b){return a.depth-b.depth}function $(a,b){function c(a,d){var e=a.children;if(e&&(i=e.length)){var f,g=null,h=-1,i;while(++h=0)f=d[e]._tree,f.prelim+=b,f.mod+=b,b+=f.shift+(c+=f.change)}function ba(a,b,c){a=a._tree,b=b._tree;var d=c/(b.number-a.number);a.change+=d,b.change-=d,b.shift+=c,b.prelim+=c,b.mod+=c}function bb(a,b,c){return a._tree.ancestor.parent==b.parent?a._tree.ancestor:c}function bc(a){return{x:a.x,y:a.y,dx:a.dx,dy:a.dy}}function bd(a,b){var c=a.x+b[3],d=a.y+b[0],e=a.dx-b[1]-b[3],f=a.dy-b[0]-b[2];return e<0&&(c+=e/2,e=0),f<0&&(d+=f/2,f=0),{x:c,y:d,dx:e,dy:f}}d3.layout={},d3.layout.bundle=function(){return function(b){var c=[],d=-1,e=b.length;while(++de&&(e=h),d.push(h)}for(g=0;g=i[0]&&o<=i[1]&&(k=g[d3.bisect(j,o,1,m)-1],k.y+=n,k.push(e[f]));return g}var a=!0,b=Number,c=y,d=w;return e.value=function(a){return arguments.length?(b=a,e):b},e.range=function(a){return arguments.length?(c=d3.functor(a),e):c},e.bins=function(a){return arguments.length?(d=typeof a=="number"?function(b){return x(b,a)}:d3.functor(a),e):d},e.frequency=function(b){return arguments.length?(a=!!b,e):a},e},d3.layout.hierarchy=function(){function e(f,h,i){var j=b.call(g,f,h),k=E?f:{data:f};k.depth=h,i.push(k);if(j&&(m=j.length)){var l=-1,m,n=k.children=[],o=0,p=h+1;while(++l0&&(ba(bb(g,a,d),a,m),i+=m,j+=m),k+=g._tree.mod,i+=e._tree.mod,l+=h._tree.mod,j+=f._tree.mod;g&&!V(f)&&(f._tree.thread=g,f._tree.mod+=k-j),e&&!U(h)&&(h._tree.thread=e,h._tree.mod+=i-l,d=a)}return d}var f=a.call(this,d,e),g=f[0];$(g,function(a,b){a._tree={ancestor:a,prelim:0,mod:0,change:0,shift:0,number:b?b._tree.number+1:0}}),h(g),i(g,-g._tree.prelim);var k=W(g,Y),l=W(g,X),m=W(g,Z),n=k.x-b(k,l)/2,o=l.x+b(l,k)/2,p=m.depth||1;return $(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=a.depth/p*c[1],delete a._tree}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=T,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},z(d,a)},d3.layout.treemap=function(){function i(a,b){var c=-1,d=a.length,e,f;while(++c0)d.push(g=f[o-1]),d.area+=g.area,(k=l(d,n))<=h?(f.pop(),h=k):(d.area-=d.pop().area,m(d,n,c,!1),n=Math.min(c.dx,c.dy),d.length=d.area=0,h=Infinity);d.length&&(m(d,n,c,!0),d.length=d.area=0),b.forEach(j)}}function k(a){var b=a.children;if(b&&b.length){var c=e(a),d=b.slice(),f,g=[];i(d,c.dx*c.dy/a.value),g.area=0;while(f=d.pop())g.push(f),g.area+=f.area,f.z!=null&&(m(g,f.z?c.dx:c.dy,c,!d.length),g.length=g.area=0);b.forEach(k)}}function l(a,b){var c=a.area,d,e=0,f=Infinity,g=-1,i=a.length;while(++ge&&(e=d)}return c*=c,b*=b,c?Math.max(b*e*h/c,c/(b*f*h)):Infinity}function m(a,c,d,e){var f=-1,g=a.length,h=d.x,i=d.y,j=c?b(a.area/c):0,k;if(c==d.dx){if(e||j>d.dy)j=j?d.dy:0;while(++fd.dx)j=j?d.dx:0;while(++f this.window.xMax) isInRange = false;
-
- return isInRange;
- }
-
- return true;
- };
-
- this.onUpdate = function(callback) {
- this.updateCallbacks.push(callback);
- };
-
- this.registerRenderer = function(renderer) {
- this._renderers = this._renderers || {};
- this._renderers[renderer.name] = renderer;
- };
-
- this.configure = function(args) {
-
- if (args.width || args.height) {
- this.setSize(args);
- }
-
- Rickshaw.keys(this.defaults).forEach( function(k) {
- this[k] = k in args ? args[k]
- : k in this ? this[k]
- : this.defaults[k];
- }, this );
-
- this.setRenderer(args.renderer || this.renderer.name, args);
- };
-
- this.setRenderer = function(name, args) {
-
- if (!this._renderers[name]) {
- throw "couldn't find renderer " + name;
- }
- this.renderer = this._renderers[name];
-
- if (typeof args == 'object') {
- this.renderer.configure(args);
- }
- };
-
- this.setSize = function(args) {
-
- args = args || {};
-
- if (typeof window !== undefined) {
- var style = window.getComputedStyle(this.element, null);
- var elementWidth = parseInt(style.getPropertyValue('width'));
- var elementHeight = parseInt(style.getPropertyValue('height'));
- }
-
- this.width = args.width || elementWidth || 400;
- this.height = args.height || elementHeight || 250;
-
- this.vis && this.vis
- .attr('width', this.width)
- .attr('height', this.height);
- }
-
- this.initialize(args);
-};
-Rickshaw.namespace('Rickshaw.Fixtures.Color');
-
-Rickshaw.Fixtures.Color = function() {
-
- this.schemes = {};
-
- this.schemes.spectrum14 = [
- '#ecb796',
- '#dc8f70',
- '#b2a470',
- '#92875a',
- '#716c49',
- '#d2ed82',
- '#bbe468',
- '#a1d05d',
- '#e7cbe6',
- '#d8aad6',
- '#a888c2',
- '#9dc2d3',
- '#649eb9',
- '#387aa3'
- ].reverse();
-
- this.schemes.spectrum2000 = [
- '#57306f',
- '#514c76',
- '#646583',
- '#738394',
- '#6b9c7d',
- '#84b665',
- '#a7ca50',
- '#bfe746',
- '#e2f528',
- '#fff726',
- '#ecdd00',
- '#d4b11d',
- '#de8800',
- '#de4800',
- '#c91515',
- '#9a0000',
- '#7b0429',
- '#580839',
- '#31082b'
- ];
-
- this.schemes.spectrum2001 = [
- '#2f243f',
- '#3c2c55',
- '#4a3768',
- '#565270',
- '#6b6b7c',
- '#72957f',
- '#86ad6e',
- '#a1bc5e',
- '#b8d954',
- '#d3e04e',
- '#ccad2a',
- '#cc8412',
- '#c1521d',
- '#ad3821',
- '#8a1010',
- '#681717',
- '#531e1e',
- '#3d1818',
- '#320a1b'
- ];
-
- this.schemes.classic9 = [
- '#423d4f',
- '#4a6860',
- '#848f39',
- '#a2b73c',
- '#ddcb53',
- '#c5a32f',
- '#7d5836',
- '#963b20',
- '#7c2626',
- '#491d37',
- '#2f254a'
- ].reverse();
-
- this.schemes.httpStatus = {
- 503: '#ea5029',
- 502: '#d23f14',
- 500: '#bf3613',
- 410: '#efacea',
- 409: '#e291dc',
- 403: '#f457e8',
- 408: '#e121d2',
- 401: '#b92dae',
- 405: '#f47ceb',
- 404: '#a82a9f',
- 400: '#b263c6',
- 301: '#6fa024',
- 302: '#87c32b',
- 307: '#a0d84c',
- 304: '#28b55c',
- 200: '#1a4f74',
- 206: '#27839f',
- 201: '#52adc9',
- 202: '#7c979f',
- 203: '#a5b8bd',
- 204: '#c1cdd1'
- };
-
- this.schemes.colorwheel = [
- '#b5b6a9',
- '#858772',
- '#785f43',
- '#96557e',
- '#4682b4',
- '#65b9ac',
- '#73c03a',
- '#cb513a'
- ].reverse();
-
- this.schemes.cool = [
- '#5e9d2f',
- '#73c03a',
- '#4682b4',
- '#7bc3b8',
- '#a9884e',
- '#c1b266',
- '#a47493',
- '#c09fb5'
- ];
-
- this.schemes.munin = [
- '#00cc00',
- '#0066b3',
- '#ff8000',
- '#ffcc00',
- '#330099',
- '#990099',
- '#ccff00',
- '#ff0000',
- '#808080',
- '#008f00',
- '#00487d',
- '#b35a00',
- '#b38f00',
- '#6b006b',
- '#8fb300',
- '#b30000',
- '#bebebe',
- '#80ff80',
- '#80c9ff',
- '#ffc080',
- '#ffe680',
- '#aa80ff',
- '#ee00cc',
- '#ff8080',
- '#666600',
- '#ffbfff',
- '#00ffcc',
- '#cc6699',
- '#999900'
- ];
-};
-Rickshaw.namespace('Rickshaw.Fixtures.RandomData');
-
-Rickshaw.Fixtures.RandomData = function(timeInterval) {
-
- var addData;
- timeInterval = timeInterval || 1;
-
- var lastRandomValue = 200;
-
- var timeBase = Math.floor(new Date().getTime() / 1000);
-
- this.addData = function(data) {
-
- var randomValue = Math.random() * 100 + 15 + lastRandomValue;
- var index = data[0].length;
-
- var counter = 1;
-
- data.forEach( function(series) {
- var randomVariance = Math.random() * 20;
- var v = randomValue / 25 + counter++
- + (Math.cos((index * counter * 11) / 960) + 2) * 15
- + (Math.cos(index / 7) + 2) * 7
- + (Math.cos(index / 17) + 2) * 1;
-
- series.push( { x: (index * timeInterval) + timeBase, y: v + randomVariance } );
- } );
-
- lastRandomValue = randomValue * .85;
- }
-};
-
-Rickshaw.namespace('Rickshaw.Fixtures.Time');
-
-Rickshaw.Fixtures.Time = function() {
-
- var tzOffset = new Date().getTimezoneOffset() * 60;
-
- var self = this;
-
- this.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
-
- this.units = [
- {
- name: 'decade',
- seconds: 86400 * 365.25 * 10,
- formatter: function(d) { return (parseInt(d.getUTCFullYear() / 10) * 10) }
- }, {
- name: 'year',
- seconds: 86400 * 365.25,
- formatter: function(d) { return d.getUTCFullYear() }
- }, {
- name: 'month',
- seconds: 86400 * 30.5,
- formatter: function(d) { return self.months[d.getUTCMonth()] }
- }, {
- name: 'week',
- seconds: 86400 * 7,
- formatter: function(d) { return self.formatDate(d) }
- }, {
- name: 'day',
- seconds: 86400,
- formatter: function(d) { return d.getUTCDate() }
- }, {
- name: '6 hour',
- seconds: 3600 * 6,
- formatter: function(d) { return self.formatTime(d) }
- }, {
- name: 'hour',
- seconds: 3600,
- formatter: function(d) { return self.formatTime(d) }
- }, {
- name: '15 minute',
- seconds: 60 * 15,
- formatter: function(d) { return self.formatTime(d) }
- }, {
- name: 'minute',
- seconds: 60,
- formatter: function(d) { return d.getUTCMinutes() }
- }, {
- name: '15 second',
- seconds: 15,
- formatter: function(d) { return d.getUTCSeconds() + 's' }
- }, {
- name: 'second',
- seconds: 1,
- formatter: function(d) { return d.getUTCSeconds() + 's' }
- }
- ];
-
- this.unit = function(unitName) {
- return this.units.filter( function(unit) { return unitName == unit.name } ).shift();
- };
-
- this.formatDate = function(d) {
- return d.toUTCString().match(/, (\w+ \w+ \w+)/)[1];
- };
-
- this.formatTime = function(d) {
- return d.toUTCString().match(/(\d+:\d+):/)[1];
- };
-
- this.ceil = function(time, unit) {
-
- if (unit.name == 'month') {
-
- var nearFuture = new Date((time + unit.seconds - 1) * 1000);
-
- var rounded = new Date(0);
- rounded.setUTCFullYear(nearFuture.getUTCFullYear());
- rounded.setUTCMonth(nearFuture.getUTCMonth());
- rounded.setUTCDate(1);
- rounded.setUTCHours(0);
- rounded.setUTCMinutes(0);
- rounded.setUTCSeconds(0);
- rounded.setUTCMilliseconds(0);
-
- return rounded.getTime() / 1000;
- }
-
- if (unit.name == 'year') {
-
- var nearFuture = new Date((time + unit.seconds - 1) * 1000);
-
- var rounded = new Date(0);
- rounded.setUTCFullYear(nearFuture.getUTCFullYear());
- rounded.setUTCMonth(0);
- rounded.setUTCDate(1);
- rounded.setUTCHours(0);
- rounded.setUTCMinutes(0);
- rounded.setUTCSeconds(0);
- rounded.setUTCMilliseconds(0);
-
- return rounded.getTime() / 1000;
- }
-
- return Math.ceil(time / unit.seconds) * unit.seconds;
- };
-};
-Rickshaw.namespace('Rickshaw.Fixtures.Number');
-
-Rickshaw.Fixtures.Number.formatKMBT = function(y) {
- let abs_y = Math.abs(y);
- if (abs_y >= 1000000000000) { return y / 1000000000000 + "T" }
- else if (abs_y >= 1000000000) { return y / 1000000000 + "B" }
- else if (abs_y >= 1000000) { return y / 1000000 + "M" }
- else if (abs_y >= 1000) { return y / 1000 + "K" }
- else if (abs_y < 1 && y > 0) { return y.toFixed(2) }
- else if (abs_y == 0) { return '' }
- else { return y }
-};
-
-Rickshaw.Fixtures.Number.formatBase1024KMGTP = function(y) {
- let abs_y = Math.abs(y);
- if (abs_y >= 1125899906842624) { return y / 1125899906842624 + "P" }
- else if (abs_y >= 1099511627776){ return y / 1099511627776 + "T" }
- else if (abs_y >= 1073741824) { return y / 1073741824 + "G" }
- else if (abs_y >= 1048576) { return y / 1048576 + "M" }
- else if (abs_y >= 1024) { return y / 1024 + "K" }
- else if (abs_y < 1 && y > 0) { return y.toFixed(2) }
- else if (abs_y == 0) { return '' }
- else { return y }
-};
-Rickshaw.namespace("Rickshaw.Color.Palette");
-
-Rickshaw.Color.Palette = function(args) {
-
- var color = new Rickshaw.Fixtures.Color();
-
- args = args || {};
- this.schemes = {};
-
- this.scheme = color.schemes[args.scheme] || args.scheme || color.schemes.colorwheel;
- this.runningIndex = 0;
- this.generatorIndex = 0;
-
- if (args.interpolatedStopCount) {
- var schemeCount = this.scheme.length - 1;
- var i, j, scheme = [];
- for (i = 0; i < schemeCount; i++) {
- scheme.push(this.scheme[i]);
- var generator = d3.interpolateHsl(this.scheme[i], this.scheme[i + 1]);
- for (j = 1; j < args.interpolatedStopCount; j++) {
- scheme.push(generator((1 / args.interpolatedStopCount) * j));
- }
- }
- scheme.push(this.scheme[this.scheme.length - 1]);
- this.scheme = scheme;
- }
- this.rotateCount = this.scheme.length;
-
- this.color = function(key) {
- return this.scheme[key] || this.scheme[this.runningIndex++] || this.interpolateColor() || '#808080';
- };
-
- this.interpolateColor = function() {
- if (!Array.isArray(this.scheme)) return;
- var color;
- if (this.generatorIndex == this.rotateCount * 2 - 1) {
- color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[0])(0.5);
- this.generatorIndex = 0;
- this.rotateCount *= 2;
- } else {
- color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[this.generatorIndex + 1])(0.5);
- this.generatorIndex++;
- }
- this.scheme.push(color);
- return color;
- };
-
-};
-Rickshaw.namespace('Rickshaw.Graph.Ajax');
-
-Rickshaw.Graph.Ajax = Rickshaw.Class.create( {
-
- initialize: function(args) {
-
- this.dataURL = args.dataURL;
-
- this.onData = args.onData || function(d) { return d };
- this.onComplete = args.onComplete || function() {};
- this.onError = args.onError || function() {};
-
- this.args = args; // pass through to Rickshaw.Graph
-
- this.request();
- },
-
- request: function() {
-
- $.ajax( {
- url: this.dataURL,
- dataType: 'json',
- success: this.success.bind(this),
- error: this.error.bind(this)
- } );
- },
-
- error: function() {
-
- console.log("error loading dataURL: " + this.dataURL);
- this.onError(this);
- },
-
- success: function(data, status) {
-
- data = this.onData(data);
- this.args.series = this._splice({ data: data, series: this.args.series });
-
- this.graph = this.graph || new Rickshaw.Graph(this.args);
- this.graph.render();
-
- this.onComplete(this);
- },
-
- _splice: function(args) {
-
- var data = args.data;
- var series = args.series;
-
- if (!args.series) return data;
-
- series.forEach( function(s) {
-
- var seriesKey = s.key || s.name;
- if (!seriesKey) throw "series needs a key or a name";
-
- data.forEach( function(d) {
-
- var dataKey = d.key || d.name;
- if (!dataKey) throw "data needs a key or a name";
-
- if (seriesKey == dataKey) {
- var properties = ['color', 'name', 'data'];
- properties.forEach( function(p) {
- if (d[p]) s[p] = d[p];
- } );
- }
- } );
- } );
-
- return series;
- }
-} );
-
-Rickshaw.namespace('Rickshaw.Graph.Annotate');
-
-Rickshaw.Graph.Annotate = function(args) {
-
- var graph = this.graph = args.graph;
- this.elements = { timeline: args.element };
-
- var self = this;
-
- this.data = {};
-
- this.elements.timeline.classList.add('rickshaw_annotation_timeline');
-
- this.add = function(time, content, end_time) {
- self.data[time] = self.data[time] || {'boxes': []};
- self.data[time].boxes.push({content: content, end: end_time});
- };
-
- this.update = function() {
-
- Rickshaw.keys(self.data).forEach( function(time) {
-
- var annotation = self.data[time];
- var left = self.graph.x(time);
-
- if (left < 0 || left > self.graph.x.range()[1]) {
- if (annotation.element) {
- annotation.line.classList.add('offscreen');
- annotation.element.style.display = 'none';
- }
-
- annotation.boxes.forEach( function(box) {
- if ( box.rangeElement ) box.rangeElement.classList.add('offscreen');
- });
-
- return;
- }
-
- if (!annotation.element) {
- var element = annotation.element = document.createElement('div');
- element.classList.add('annotation');
- this.elements.timeline.appendChild(element);
- element.addEventListener('click', function(e) {
- element.classList.toggle('active');
- annotation.line.classList.toggle('active');
- annotation.boxes.forEach( function(box) {
- if ( box.rangeElement ) box.rangeElement.classList.toggle('active');
- });
- }, false);
-
- }
-
- annotation.element.style.left = left + 'px';
- annotation.element.style.display = 'block';
-
- annotation.boxes.forEach( function(box) {
-
-
- var element = box.element;
-
- if (!element) {
- element = box.element = document.createElement('div');
- element.classList.add('content');
- element.innerHTML = box.content;
- annotation.element.appendChild(element);
-
- annotation.line = document.createElement('div');
- annotation.line.classList.add('annotation_line');
- self.graph.element.appendChild(annotation.line);
-
- if ( box.end ) {
- box.rangeElement = document.createElement('div');
- box.rangeElement.classList.add('annotation_range');
- self.graph.element.appendChild(box.rangeElement);
- }
-
- }
-
- if ( box.end ) {
-
- var annotationRangeStart = left;
- var annotationRangeEnd = Math.min( self.graph.x(box.end), self.graph.x.range()[1] );
-
- // annotation makes more sense at end
- if ( annotationRangeStart > annotationRangeEnd ) {
- annotationRangeEnd = left;
- annotationRangeStart = Math.max( self.graph.x(box.end), self.graph.x.range()[0] );
- }
-
- var annotationRangeWidth = annotationRangeEnd - annotationRangeStart;
-
- box.rangeElement.style.left = annotationRangeStart + 'px';
- box.rangeElement.style.width = annotationRangeWidth + 'px'
-
- box.rangeElement.classList.remove('offscreen');
- }
-
- annotation.line.classList.remove('offscreen');
- annotation.line.style.left = left + 'px';
- } );
- }, this );
- };
-
- this.graph.onUpdate( function() { self.update() } );
-};
-Rickshaw.namespace('Rickshaw.Graph.Axis.Time');
-
-Rickshaw.Graph.Axis.Time = function(args) {
-
- var self = this;
-
- this.graph = args.graph;
- this.elements = [];
- this.ticksTreatment = args.ticksTreatment || 'plain';
- this.fixedTimeUnit = args.timeUnit;
-
- var time = new Rickshaw.Fixtures.Time();
-
- this.appropriateTimeUnit = function() {
-
- var unit;
- var units = time.units;
-
- var domain = this.graph.x.domain();
- var rangeSeconds = domain[1] - domain[0];
-
- units.forEach( function(u) {
- if (Math.floor(rangeSeconds / u.seconds) >= 2) {
- unit = unit || u;
- }
- } );
-
- return (unit || time.units[time.units.length - 1]);
- };
-
- this.tickOffsets = function() {
-
- var domain = this.graph.x.domain();
-
- var unit = this.fixedTimeUnit || this.appropriateTimeUnit();
- var count = Math.ceil((domain[1] - domain[0]) / unit.seconds);
-
- var runningTick = domain[0];
-
- var offsets = [];
-
- for (var i = 0; i < count; i++) {
-
- var tickValue = time.ceil(runningTick, unit);
- runningTick = tickValue + unit.seconds / 2;
-
- offsets.push( { value: tickValue, unit: unit } );
- }
-
- return offsets;
- };
-
- this.render = function() {
-
- this.elements.forEach( function(e) {
- e.parentNode.removeChild(e);
- } );
-
- this.elements = [];
-
- var offsets = this.tickOffsets();
-
- offsets.forEach( function(o) {
-
- if (self.graph.x(o.value) > self.graph.x.range()[1]) return;
-
- var element = document.createElement('div');
- element.style.left = self.graph.x(o.value) + 'px';
- element.classList.add('x_tick');
- element.classList.add(self.ticksTreatment);
-
- var title = document.createElement('div');
- title.classList.add('title');
- title.innerHTML = o.unit.formatter(new Date(o.value * 1000));
- element.appendChild(title);
-
- self.graph.element.appendChild(element);
- self.elements.push(element);
-
- } );
- };
-
- this.graph.onUpdate( function() { self.render() } );
-};
-
-Rickshaw.namespace('Rickshaw.Graph.Axis.X');
-
-Rickshaw.Graph.Axis.X = function(args) {
-
- var self = this;
- var berthRate = 0.10;
-
- this.initialize = function(args) {
-
- this.graph = args.graph;
- this.orientation = args.orientation || 'top';
-
- var pixelsPerTick = args.pixelsPerTick || 75;
- this.ticks = args.ticks || Math.floor(this.graph.width / pixelsPerTick);
- this.tickSize = args.tickSize || 4;
- this.ticksTreatment = args.ticksTreatment || 'plain';
-
- if (args.element) {
-
- this.element = args.element;
- this._discoverSize(args.element, args);
-
- this.vis = d3.select(args.element)
- .append("svg:svg")
- .attr('height', this.height)
- .attr('width', this.width)
- .attr('class', 'rickshaw_graph x_axis_d3');
-
- this.element = this.vis[0][0];
- this.element.style.position = 'relative';
-
- this.setSize({ width: args.width, height: args.height });
-
- } else {
- this.vis = this.graph.vis;
- }
-
- this.graph.onUpdate( function() { self.render() } );
- };
-
- this.setSize = function(args) {
-
- args = args || {};
- if (!this.element) return;
-
- this._discoverSize(this.element.parentNode, args);
-
- this.vis
- .attr('height', this.height)
- .attr('width', this.width * (1 + berthRate));
-
- var berth = Math.floor(this.width * berthRate / 2);
- this.element.style.left = -1 * berth + 'px';
- };
-
- this.render = function() {
-
- if (this.graph.width !== this._renderWidth) this.setSize({ auto: true });
-
- var axis = d3.svg.axis().scale(this.graph.x).orient(this.orientation);
- axis.tickFormat( args.tickFormat || function(x) { return x } );
-
- var berth = Math.floor(this.width * berthRate / 2) || 0;
-
- if (this.orientation == 'top') {
- var yOffset = this.height || this.graph.height;
- var transform = 'translate(' + berth + ',' + yOffset + ')';
- } else {
- var transform = 'translate(' + berth + ', 0)';
- }
-
- if (this.element) {
- this.vis.selectAll('*').remove();
- }
-
- this.vis
- .append("svg:g")
- .attr("class", ["x_ticks_d3", this.ticksTreatment].join(" "))
- .attr("transform", transform)
- .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));
-
- var gridSize = (this.orientation == 'bottom' ? 1 : -1) * this.graph.height;
-
- this.graph.vis
- .append("svg:g")
- .attr("class", "x_grid_d3")
- .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize));
-
- this._renderHeight = this.graph.height;
- };
-
- this._discoverSize = function(element, args) {
-
- if (typeof window !== 'undefined') {
-
- var style = window.getComputedStyle(element, null);
- var elementHeight = parseInt(style.getPropertyValue('height'));
-
- if (!args.auto) {
- var elementWidth = parseInt(style.getPropertyValue('width'));
- }
- }
-
- this.width = (args.width || elementWidth || this.graph.width) * (1 + berthRate);
- this.height = args.height || elementHeight || 40;
- };
-
- this.initialize(args);
-};
-
-Rickshaw.namespace('Rickshaw.Graph.Axis.Y');
-
-Rickshaw.Graph.Axis.Y = function(args) {
-
- var self = this;
- var berthRate = 0.10;
-
- this.initialize = function(args) {
-
- this.graph = args.graph;
- this.orientation = args.orientation || 'right';
-
- var pixelsPerTick = args.pixelsPerTick || 75;
- this.ticks = args.ticks || Math.floor(this.graph.height / pixelsPerTick);
- this.tickSize = args.tickSize || 4;
- this.ticksTreatment = args.ticksTreatment || 'plain';
-
- if (args.element) {
-
- this.element = args.element;
- this.vis = d3.select(args.element)
- .append("svg:svg")
- .attr('class', 'rickshaw_graph y_axis');
-
- this.element = this.vis[0][0];
- this.element.style.position = 'relative';
-
- this.setSize({ width: args.width, height: args.height });
-
- } else {
- this.vis = this.graph.vis;
- }
-
- this.graph.onUpdate( function() { self.render() } );
- };
-
- this.setSize = function(args) {
-
- args = args || {};
-
- if (!this.element) return;
-
- if (typeof window !== 'undefined') {
-
- var style = window.getComputedStyle(this.element.parentNode, null);
- var elementWidth = parseInt(style.getPropertyValue('width'));
-
- if (!args.auto) {
- var elementHeight = parseInt(style.getPropertyValue('height'));
- }
- }
-
- this.width = args.width || elementWidth || this.graph.width * berthRate;
- this.height = args.height || elementHeight || this.graph.height;
-
- this.vis
- .attr('width', this.width)
- .attr('height', this.height * (1 + berthRate));
-
- var berth = this.height * berthRate;
- this.element.style.top = -1 * berth + 'px';
- };
-
- this.render = function() {
-
- if (this.graph.height !== this._renderHeight) this.setSize({ auto: true });
-
- var axis = d3.svg.axis().scale(this.graph.y).orient(this.orientation);
- axis.tickFormat( args.tickFormat || function(y) { return y } );
-
- if (this.orientation == 'left') {
- var berth = this.height * berthRate;
- var transform = 'translate(' + this.width + ', ' + berth + ')';
- }
-
- if (this.element) {
- this.vis.selectAll('*').remove();
- }
-
- this.vis
- .append("svg:g")
- .attr("class", ["y_ticks", this.ticksTreatment].join(" "))
- .attr("transform", transform)
- .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize))
-
- var gridSize = (this.orientation == 'right' ? 1 : -1) * this.graph.width;
-
- this.graph.vis
- .append("svg:g")
- .attr("class", "y_grid")
- .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize));
-
- this._renderHeight = this.graph.height;
- };
-
- this.initialize(args);
-};
-
-Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Highlight');
-
-Rickshaw.Graph.Behavior.Series.Highlight = function(args) {
-
- this.graph = args.graph;
- this.legend = args.legend;
-
- var self = this;
-
- var colorSafe = {};
- var activeLine = null;
-
- this.addHighlightEvents = function (l) {
-
- l.element.addEventListener( 'mouseover', function(e) {
-
- if (activeLine) return;
- else activeLine = l;
-
- self.legend.lines.forEach( function(line, index) {
-
- if (l === line) {
-
- // if we're not in a stacked renderer bring active line to the top
- if (index > 0 && self.graph.renderer.unstack) {
-
- var seriesIndex = self.graph.series.length - index - 1;
- line.originalIndex = seriesIndex;
-
- var series = self.graph.series.splice(seriesIndex, 1)[0];
- self.graph.series.push(series);
- }
- return;
- }
-
- colorSafe[line.series.name] = colorSafe[line.series.name] || line.series.color;
- line.series.color = d3.interpolateRgb(line.series.color, d3.rgb('#d8d8d8'))(0.8).toString();
- } );
-
- self.graph.update();
-
- }, false );
-
- l.element.addEventListener( 'mouseout', function(e) {
-
- if (!activeLine) return;
- else activeLine = null;
-
- self.legend.lines.forEach( function(line) {
-
- // return reordered series to its original place
- if (l === line && line.hasOwnProperty('originalIndex')) {
-
- var series = self.graph.series.pop();
- self.graph.series.splice(line.originalIndex, 0, series);
- delete line['originalIndex'];
- }
-
- if (colorSafe[line.series.name]) {
- line.series.color = colorSafe[line.series.name];
- }
- } );
-
- self.graph.update();
-
- }, false );
- };
-
- if (this.legend) {
- this.legend.lines.forEach( function(l) {
- self.addHighlightEvents(l);
- } );
- }
-
-};
-Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Order');
-
-Rickshaw.Graph.Behavior.Series.Order = function(args) {
-
- this.graph = args.graph;
- this.legend = args.legend;
-
- var self = this;
-
- $(function() {
- $(self.legend.list).sortable( {
- containment: 'parent',
- tolerance: 'pointer',
- update: function( event, ui ) {
- var series = [];
- $(self.legend.list).find('li').each( function(index, item) {
- if (!item.series) return;
- series.push(item.series);
- } );
-
- for (var i = self.graph.series.length - 1; i >= 0; i--) {
- self.graph.series[i] = series.shift();
- }
-
- self.graph.update();
- }
- } );
- $(self.legend.list).disableSelection();
- });
-
- //hack to make jquery-ui sortable behave
- this.graph.onUpdate( function() {
- var h = window.getComputedStyle(self.legend.element).height;
- self.legend.element.style.height = h;
- } );
-};
-Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Toggle');
-
-Rickshaw.Graph.Behavior.Series.Toggle = function(args) {
-
- this.graph = args.graph;
- this.legend = args.legend;
-
- var self = this;
-
- this.addAnchor = function(line) {
- var anchor = document.createElement('a');
- anchor.innerHTML = '✔';
- anchor.classList.add('action');
- line.element.insertBefore(anchor, line.element.firstChild);
-
- anchor.onclick = function(e) {
- if (line.series.disabled) {
- line.series.enable();
- line.element.classList.remove('disabled');
- } else {
- line.series.disable();
- line.element.classList.add('disabled');
- }
- }
-
- var label = line.element.getElementsByTagName('span')[0];
- label.onclick = function(e){
-
- var disableAllOtherLines = line.series.disabled;
- if ( ! disableAllOtherLines ) {
- for ( var i = 0; i < self.legend.lines.length; i++ ) {
- var l = self.legend.lines[i];
- if ( line.series === l.series ) {
- // noop
- } else if ( l.series.disabled ) {
- // noop
- } else {
- disableAllOtherLines = true;
- break;
- }
- }
- }
-
- // show all or none
- if ( disableAllOtherLines ) {
-
- // these must happen first or else we try ( and probably fail ) to make a no line graph
- line.series.enable();
- line.element.classList.remove('disabled');
-
- self.legend.lines.forEach(function(l){
- if ( line.series === l.series ) {
- // noop
- } else {
- l.series.disable();
- l.element.classList.add('disabled');
- }
- });
-
- } else {
-
- self.legend.lines.forEach(function(l){
- l.series.enable();
- l.element.classList.remove('disabled');
- });
-
- }
-
- };
-
- };
-
- if (this.legend) {
-
- $(this.legend.list).sortable( {
- start: function(event, ui) {
- ui.item.bind('no.onclick',
- function(event) {
- event.preventDefault();
- }
- );
- },
- stop: function(event, ui) {
- setTimeout(function(){
- ui.item.off('no.onclick');
- }, 250);
- }
- })
-
- this.legend.lines.forEach( function(l) {
- self.addAnchor(l);
- } );
- }
-
- this._addBehavior = function() {
-
- this.graph.series.forEach( function(s) {
-
- s.disable = function() {
-
- if (self.graph.series.length <= 1) {
- throw('only one series left');
- }
-
- s.disabled = true;
- self.graph.update();
- };
-
- s.enable = function() {
- s.disabled = false;
- self.graph.update();
- };
- } );
- };
- this._addBehavior();
-
- this.updateBehaviour = function () { this._addBehavior() };
-
-};
-Rickshaw.namespace('Rickshaw.Graph.HoverDetail');
-
-Rickshaw.Graph.HoverDetail = Rickshaw.Class.create({
-
- initialize: function(args) {
-
- var graph = this.graph = args.graph;
-
- this.xFormatter = args.xFormatter || function(x) {
- return new Date( x * 1000 ).toUTCString();
- };
-
- this.yFormatter = args.yFormatter || function(y) {
- return y === null ? y : y.toFixed(2);
- };
-
- var element = this.element = document.createElement('div');
- element.className = 'detail';
-
- this.visible = true;
- graph.element.appendChild(element);
-
- this.lastEvent = null;
- this._addListeners();
-
- this.onShow = args.onShow;
- this.onHide = args.onHide;
- this.onRender = args.onRender;
-
- this.formatter = args.formatter || this.formatter;
-
- },
-
- formatter: function(series, x, y, formattedX, formattedY, d) {
- return series.name + ': ' + formattedY;
- },
-
- update: function(e) {
-
- e = e || this.lastEvent;
- if (!e) return;
- this.lastEvent = e;
-
- if (!e.target.nodeName.match(/^(path|svg|rect)$/)) return;
-
- var graph = this.graph;
-
- var eventX = e.offsetX || e.layerX;
- var eventY = e.offsetY || e.layerY;
-
- var j = 0;
- var points = [];
- var nearestPoint;
-
- this.graph.series.active().forEach( function(series) {
-
- var data = this.graph.stackedData[j++];
-
- var domainX = graph.x.invert(eventX);
-
- var domainIndexScale = d3.scale.linear()
- .domain([data[0].x, data.slice(-1)[0].x])
- .range([0, data.length - 1]);
-
- var approximateIndex = Math.round(domainIndexScale(domainX));
- var dataIndex = Math.min(approximateIndex || 0, data.length - 1);
-
- for (var i = approximateIndex; i < data.length - 1;) {
-
- if (!data[i] || !data[i + 1]) break;
- if (data[i].x <= domainX && data[i + 1].x > domainX) { dataIndex = i; break }
-
- if (data[i + 1].x <= domainX) { i++ } else { i-- }
- }
-
- if (dataIndex < 0) dataIndex = 0;
- var value = data[dataIndex];
-
- var distance = Math.sqrt(
- Math.pow(Math.abs(graph.x(value.x) - eventX), 2) +
- Math.pow(Math.abs(graph.y(value.y + value.y0) - eventY), 2)
- );
-
- var xFormatter = series.xFormatter || this.xFormatter;
- var yFormatter = series.yFormatter || this.yFormatter;
-
- var point = {
- formattedXValue: xFormatter(value.x),
- formattedYValue: yFormatter(value.y),
- series: series,
- value: value,
- distance: distance,
- order: j,
- name: series.name
- };
-
- if (!nearestPoint || distance < nearestPoint.distance) {
- nearestPoint = point;
- }
-
- points.push(point);
-
- }, this );
-
-
- nearestPoint.active = true;
-
- var domainX = nearestPoint.value.x;
- var formattedXValue = nearestPoint.formattedXValue;
-
- this.element.innerHTML = '';
- this.element.style.left = graph.x(domainX) + 'px';
-
- this.visible && this.render( {
- points: points,
- detail: points, // for backwards compatibility
- mouseX: eventX,
- mouseY: eventY,
- formattedXValue: formattedXValue,
- domainX: domainX
- } );
- },
-
- hide: function() {
- this.visible = false;
- this.element.classList.add('inactive');
-
- if (typeof this.onHide == 'function') {
- this.onHide();
- }
- },
-
- show: function() {
- this.visible = true;
- this.element.classList.remove('inactive');
-
- if (typeof this.onShow == 'function') {
- this.onShow();
- }
- },
-
- render: function(args) {
-
- var graph = this.graph;
- var points = args.points;
- var point = points.filter( function(p) { return p.active } ).shift();
-
- if (point.value.y === null) return;
-
- var formattedXValue = this.xFormatter(point.value.x);
- var formattedYValue = this.yFormatter(point.value.y);
-
- this.element.innerHTML = '';
- this.element.style.left = graph.x(point.value.x) + 'px';
-
- var xLabel = document.createElement('div');
-
- xLabel.className = 'x_label';
- xLabel.innerHTML = formattedXValue;
- this.element.appendChild(xLabel);
-
- var item = document.createElement('div');
-
- item.className = 'item';
- item.innerHTML = this.formatter(point.series, point.value.x, point.value.y, formattedXValue, formattedYValue, point);
- item.style.top = this.graph.y(point.value.y0 + point.value.y) + 'px';
-
- this.element.appendChild(item);
-
- var dot = document.createElement('div');
-
- dot.className = 'dot';
- dot.style.top = item.style.top;
- dot.style.borderColor = point.series.color;
-
- this.element.appendChild(dot);
-
- if (point.active) {
- item.className = 'item active';
- dot.className = 'dot active';
- }
-
- this.show();
-
- if (typeof this.onRender == 'function') {
- this.onRender(args);
- }
- },
-
- _addListeners: function() {
-
- this.graph.element.addEventListener(
- 'mousemove',
- function(e) {
- this.visible = true;
- this.update(e)
- }.bind(this),
- false
- );
-
- this.graph.onUpdate( function() { this.update() }.bind(this) );
-
- this.graph.element.addEventListener(
- 'mouseout',
- function(e) {
- if (e.relatedTarget && !(e.relatedTarget.compareDocumentPosition(this.graph.element) & Node.DOCUMENT_POSITION_CONTAINS)) {
- this.hide();
- }
- }.bind(this),
- false
- );
- }
-});
-
-Rickshaw.namespace('Rickshaw.Graph.JSONP');
-
-Rickshaw.Graph.JSONP = Rickshaw.Class.create( Rickshaw.Graph.Ajax, {
-
- request: function() {
-
- $.ajax( {
- url: this.dataURL,
- dataType: 'jsonp',
- success: this.success.bind(this),
- error: this.error.bind(this)
- } );
- }
-} );
-Rickshaw.namespace('Rickshaw.Graph.Legend');
-
-Rickshaw.Graph.Legend = function(args) {
-
- var element = this.element = args.element;
- var graph = this.graph = args.graph;
-
- var self = this;
-
- element.classList.add('rickshaw_legend');
-
- var list = this.list = document.createElement('ul');
- element.appendChild(list);
-
- var series = graph.series
- .map( function(s) { return s } )
-
- if (!args.naturalOrder) {
- series = series.reverse();
- }
-
- this.lines = [];
-
- this.addLine = function (series) {
- var line = document.createElement('li');
- line.className = 'line';
- if (series.disabled) {
- line.className += ' disabled';
- }
-
- var swatch = document.createElement('div');
- swatch.className = 'swatch';
- swatch.style.backgroundColor = series.color;
-
- line.appendChild(swatch);
-
- var label = document.createElement('span');
- label.className = 'label';
- label.innerHTML = series.name;
-
- line.appendChild(label);
- list.appendChild(line);
-
- line.series = series;
-
- if (series.noLegend) {
- line.style.display = 'none';
- }
-
- var _line = { element: line, series: series };
- if (self.shelving) {
- self.shelving.addAnchor(_line);
- self.shelving.updateBehaviour();
- }
- if (self.highlighter) {
- self.highlighter.addHighlightEvents(_line);
- }
- self.lines.push(_line);
- };
-
- series.forEach( function(s) {
- self.addLine(s);
- } );
-
- graph.onUpdate( function() {} );
-};
-Rickshaw.namespace('Rickshaw.Graph.RangeSlider');
-
-Rickshaw.Graph.RangeSlider = function(args) {
-
- var element = this.element = args.element;
- var graph = this.graph = args.graph;
-
- $( function() {
- $(element).slider( {
-
- range: true,
- min: graph.dataDomain()[0],
- max: graph.dataDomain()[1],
- values: [
- graph.dataDomain()[0],
- graph.dataDomain()[1]
- ],
- slide: function( event, ui ) {
-
- graph.window.xMin = ui.values[0];
- graph.window.xMax = ui.values[1];
- graph.update();
-
- // if we're at an extreme, stick there
- if (graph.dataDomain()[0] == ui.values[0]) {
- graph.window.xMin = undefined;
- }
- if (graph.dataDomain()[1] == ui.values[1]) {
- graph.window.xMax = undefined;
- }
- }
- } );
- } );
-
- element[0].style.width = graph.width + 'px';
-
- graph.onUpdate( function() {
-
- var values = $(element).slider('option', 'values');
-
- $(element).slider('option', 'min', graph.dataDomain()[0]);
- $(element).slider('option', 'max', graph.dataDomain()[1]);
-
- if (graph.window.xMin == undefined) {
- values[0] = graph.dataDomain()[0];
- }
- if (graph.window.xMax == undefined) {
- values[1] = graph.dataDomain()[1];
- }
-
- $(element).slider('option', 'values', values);
-
- } );
-};
-
-Rickshaw.namespace("Rickshaw.Graph.Renderer");
-
-Rickshaw.Graph.Renderer = Rickshaw.Class.create( {
-
- initialize: function(args) {
- this.graph = args.graph;
- this.tension = args.tension || this.tension;
- this.graph.unstacker = this.graph.unstacker || new Rickshaw.Graph.Unstacker( { graph: this.graph } );
- this.configure(args);
- },
-
- seriesPathFactory: function() {
- //implement in subclass
- },
-
- seriesStrokeFactory: function() {
- // implement in subclass
- },
-
- defaults: function() {
- return {
- tension: 0.8,
- strokeWidth: 2,
- unstack: true,
- padding: { top: 0.01, right: 0, bottom: 0.01, left: 0 },
- stroke: false,
- fill: false
- };
- },
-
- domain: function() {
-
- var values = { xMin: [], xMax: [], y: [] };
-
- var stackedData = this.graph.stackedData || this.graph.stackData();
- var firstPoint = stackedData[0][0];
-
- var xMin = firstPoint.x;
- var xMax = firstPoint.x
-
- var yMin = firstPoint.y + firstPoint.y0;
- var yMax = firstPoint.y + firstPoint.y0;
-
- stackedData.forEach( function(series) {
-
- series.forEach( function(d) {
-
- if (d.y == undefined) return;
-
- var y = d.y + d.y0;
-
- if (y < yMin) yMin = y;
- if (y > yMax) yMax = y;
- } );
-
- if (series[0].x < xMin) xMin = series[0].x;
- if (series[series.length - 1].x > xMax) xMax = series[series.length - 1].x;
- } );
-
- xMin -= (xMax - xMin) * this.padding.left;
- xMax += (xMax - xMin) * this.padding.right;
-
- yMin = this.graph.min === 'auto' ? yMin : this.graph.min || 0;
- yMax = this.graph.max === undefined ? yMax : this.graph.max;
-
- if (this.graph.min === 'auto' || yMin < 0) {
- yMin -= (yMax - yMin) * this.padding.bottom;
- }
-
- if (this.graph.max === undefined) {
- yMax += (yMax - yMin) * this.padding.top;
- }
-
- return { x: [xMin, xMax], y: [yMin, yMax] };
- },
-
- render: function() {
-
- var graph = this.graph;
-
- graph.vis.selectAll('*').remove();
-
- var nodes = graph.vis.selectAll("path")
- .data(this.graph.stackedData)
- .enter().append("svg:path")
- .attr("d", this.seriesPathFactory());
-
- var i = 0;
- graph.series.forEach( function(series) {
- if (series.disabled) return;
- series.path = nodes[0][i++];
- this._styleSeries(series);
- }, this );
- },
-
- _styleSeries: function(series) {
-
- var fill = this.fill ? series.color : 'none';
- var stroke = this.stroke ? series.color : 'none';
-
- series.path.setAttribute('fill', fill);
- series.path.setAttribute('stroke', stroke);
- series.path.setAttribute('stroke-width', this.strokeWidth);
- series.path.setAttribute('class', series.className);
- },
-
- configure: function(args) {
-
- args = args || {};
-
- Rickshaw.keys(this.defaults()).forEach( function(key) {
-
- if (!args.hasOwnProperty(key)) {
- this[key] = this[key] || this.graph[key] || this.defaults()[key];
- return;
- }
-
- if (typeof this.defaults()[key] == 'object') {
-
- Rickshaw.keys(this.defaults()[key]).forEach( function(k) {
-
- this[key][k] =
- args[key][k] !== undefined ? args[key][k] :
- this[key][k] !== undefined ? this[key][k] :
- this.defaults()[key][k];
- }, this );
-
- } else {
- this[key] =
- args[key] !== undefined ? args[key] :
- this[key] !== undefined ? this[key] :
- this.graph[key] !== undefined ? this.graph[key] :
- this.defaults()[key];
- }
-
- }, this );
- },
-
- setStrokeWidth: function(strokeWidth) {
- if (strokeWidth !== undefined) {
- this.strokeWidth = strokeWidth;
- }
- },
-
- setTension: function(tension) {
- if (tension !== undefined) {
- this.tension = tension;
- }
- }
-} );
-
-Rickshaw.namespace('Rickshaw.Graph.Renderer.Line');
-
-Rickshaw.Graph.Renderer.Line = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
-
- name: 'line',
-
- defaults: function($super) {
-
- return Rickshaw.extend( $super(), {
- unstack: true,
- fill: false,
- stroke: true
- } );
- },
-
- seriesPathFactory: function() {
-
- var graph = this.graph;
-
- var factory = d3.svg.line()
- .x( function(d) { return graph.x(d.x) } )
- .y( function(d) { return graph.y(d.y) } )
- .interpolate(this.graph.interpolation).tension(this.tension)
-
- factory.defined && factory.defined( function(d) { return d.y !== null } );
- return factory;
- }
-} );
-
-Rickshaw.namespace('Rickshaw.Graph.Renderer.Stack');
-
-Rickshaw.Graph.Renderer.Stack = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
-
- name: 'stack',
-
- defaults: function($super) {
-
- return Rickshaw.extend( $super(), {
- fill: true,
- stroke: false,
- unstack: false
- } );
- },
-
- seriesPathFactory: function() {
-
- var graph = this.graph;
-
- var factory = d3.svg.area()
- .x( function(d) { return graph.x(d.x) } )
- .y0( function(d) { return graph.y(d.y0) } )
- .y1( function(d) { return graph.y(d.y + d.y0) } )
- .interpolate(this.graph.interpolation).tension(this.tension);
-
- factory.defined && factory.defined( function(d) { return d.y !== null } );
- return factory;
- }
-} );
-
-Rickshaw.namespace('Rickshaw.Graph.Renderer.Bar');
-
-Rickshaw.Graph.Renderer.Bar = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
-
- name: 'bar',
-
- defaults: function($super) {
-
- var defaults = Rickshaw.extend( $super(), {
- gapSize: 0.05,
- unstack: false
- } );
-
- delete defaults.tension;
- return defaults;
- },
-
- initialize: function($super, args) {
- args = args || {};
- this.gapSize = args.gapSize || this.gapSize;
- $super(args);
- },
-
- domain: function($super) {
-
- var domain = $super();
-
- var frequentInterval = this._frequentInterval();
- domain.x[1] += parseInt(frequentInterval.magnitude);
-
- return domain;
- },
-
- barWidth: function() {
-
- var stackedData = this.graph.stackedData || this.graph.stackData();
- var data = stackedData.slice(-1).shift();
-
- var frequentInterval = this._frequentInterval();
- var barWidth = this.graph.x(data[0].x + frequentInterval.magnitude * (1 - this.gapSize));
-
- return barWidth;
- },
-
- render: function() {
-
- var graph = this.graph;
-
- graph.vis.selectAll('*').remove();
-
- var barWidth = this.barWidth();
- var barXOffset = 0;
-
- var activeSeriesCount = graph.series.filter( function(s) { return !s.disabled; } ).length;
- var seriesBarWidth = this.unstack ? barWidth / activeSeriesCount : barWidth;
-
- var transform = function(d) {
- // add a matrix transform for negative values
- var matrix = [ 1, 0, 0, (d.y < 0 ? -1 : 1), 0, (d.y < 0 ? graph.y.magnitude(Math.abs(d.y)) * 2 : 0) ];
- return "matrix(" + matrix.join(',') + ")";
- };
-
- graph.series.forEach( function(series) {
-
- if (series.disabled) return;
-
- var nodes = graph.vis.selectAll("path")
- .data(series.stack.filter( function(d) { return d.y !== null } ))
- .enter().append("svg:rect")
- .attr("x", function(d) { return graph.x(d.x) + barXOffset })
- .attr("y", function(d) { return (graph.y(d.y0 + Math.abs(d.y))) * (d.y < 0 ? -1 : 1 ) })
- .attr("width", seriesBarWidth)
- .attr("height", function(d) { return graph.y.magnitude(Math.abs(d.y)) })
- .attr("transform", transform);
-
- Array.prototype.forEach.call(nodes[0], function(n) {
- n.setAttribute('fill', series.color);
- } );
-
- if (this.unstack) barXOffset += seriesBarWidth;
-
- }, this );
- },
-
- _frequentInterval: function() {
-
- var stackedData = this.graph.stackedData || this.graph.stackData();
- var data = stackedData.slice(-1).shift();
-
- var intervalCounts = {};
-
- for (var i = 0; i < data.length - 1; i++) {
- var interval = data[i + 1].x - data[i].x;
- intervalCounts[interval] = intervalCounts[interval] || 0;
- intervalCounts[interval]++;
- }
-
- var frequentInterval = { count: 0 };
-
- Rickshaw.keys(intervalCounts).forEach( function(i) {
- if (frequentInterval.count < intervalCounts[i]) {
-
- frequentInterval = {
- count: intervalCounts[i],
- magnitude: i
- };
- }
- } );
-
- //this._frequentInterval = function() { return frequentInterval };
-
- return frequentInterval;
- }
-} );
-
-Rickshaw.namespace('Rickshaw.Graph.Renderer.Area');
-
-Rickshaw.Graph.Renderer.Area = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
-
- name: 'area',
-
- defaults: function($super) {
-
- return Rickshaw.extend( $super(), {
- unstack: false,
- fill: false,
- stroke: false
- } );
- },
-
- seriesPathFactory: function() {
-
- var graph = this.graph;
-
- var factory = d3.svg.area()
- .x( function(d) { return graph.x(d.x) } )
- .y0( function(d) { return graph.y(d.y0) } )
- .y1( function(d) { return graph.y(d.y + d.y0) } )
- .interpolate(graph.interpolation).tension(this.tension)
-
- factory.defined && factory.defined( function(d) { return d.y !== null } );
- return factory;
- },
-
- seriesStrokeFactory: function() {
-
- var graph = this.graph;
-
- var factory = d3.svg.line()
- .x( function(d) { return graph.x(d.x) } )
- .y( function(d) { return graph.y(d.y + d.y0) } )
- .interpolate(graph.interpolation).tension(this.tension)
-
- factory.defined && factory.defined( function(d) { return d.y !== null } );
- return factory;
- },
-
- render: function() {
-
- var graph = this.graph;
-
- graph.vis.selectAll('*').remove();
-
- // insert or stacked areas so strokes lay on top of areas
- var method = this.unstack ? 'append' : 'insert';
-
- var nodes = graph.vis.selectAll("path")
- .data(this.graph.stackedData)
- .enter()[method]("svg:g", 'g');
-
- nodes.append("svg:path")
- .attr("d", this.seriesPathFactory())
- .attr("class", 'area');
-
- if (this.stroke) {
- nodes.append("svg:path")
- .attr("d", this.seriesStrokeFactory())
- .attr("class", 'line');
- }
-
- var i = 0;
- graph.series.forEach( function(series) {
- if (series.disabled) return;
- series.path = nodes[0][i++];
- this._styleSeries(series);
- }, this );
- },
-
- _styleSeries: function(series) {
-
- if (!series.path) return;
-
- d3.select(series.path).select('.area')
- .attr('fill', series.color);
-
- if (this.stroke) {
- d3.select(series.path).select('.line')
- .attr('fill', 'none')
- .attr('stroke', series.stroke || d3.interpolateRgb(series.color, 'black')(0.125))
- .attr('stroke-width', this.strokeWidth);
- }
-
- if (series.className) {
- series.path.setAttribute('class', series.className);
- }
- }
-} );
-
-Rickshaw.namespace('Rickshaw.Graph.Renderer.ScatterPlot');
-
-Rickshaw.Graph.Renderer.ScatterPlot = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
-
- name: 'scatterplot',
-
- defaults: function($super) {
-
- return Rickshaw.extend( $super(), {
- unstack: true,
- fill: true,
- stroke: false,
- padding:{ top: 0.01, right: 0.01, bottom: 0.01, left: 0.01 },
- dotSize: 4
- } );
- },
-
- initialize: function($super, args) {
- $super(args);
- },
-
- render: function() {
-
- var graph = this.graph;
-
- graph.vis.selectAll('*').remove();
-
- graph.series.forEach( function(series) {
-
- if (series.disabled) return;
-
- var nodes = graph.vis.selectAll("path")
- .data(series.stack.filter( function(d) { return d.y !== null } ))
- .enter().append("svg:circle")
- .attr("cx", function(d) { return graph.x(d.x) })
- .attr("cy", function(d) { return graph.y(d.y) })
- .attr("r", function(d) { return ("r" in d) ? d.r : graph.renderer.dotSize});
-
- Array.prototype.forEach.call(nodes[0], function(n) {
- n.setAttribute('fill', series.color);
- } );
-
- }, this );
- }
-} );
-Rickshaw.namespace('Rickshaw.Graph.Smoother');
-
-Rickshaw.Graph.Smoother = function(args) {
-
- this.graph = args.graph;
- this.element = args.element;
-
- var self = this;
-
- this.aggregationScale = 1;
-
- if (this.element) {
-
- $( function() {
- $(self.element).slider( {
- min: 1,
- max: 100,
- slide: function( event, ui ) {
- self.setScale(ui.value);
- self.graph.update();
- }
- } );
- } );
- }
-
- self.graph.stackData.hooks.data.push( {
- name: 'smoother',
- orderPosition: 50,
- f: function(data) {
-
- if (self.aggregationScale == 1) return data;
-
- var aggregatedData = [];
-
- data.forEach( function(seriesData) {
-
- var aggregatedSeriesData = [];
-
- while (seriesData.length) {
-
- var avgX = 0, avgY = 0;
- var slice = seriesData.splice(0, self.aggregationScale);
-
- slice.forEach( function(d) {
- avgX += d.x / slice.length;
- avgY += d.y / slice.length;
- } );
-
- aggregatedSeriesData.push( { x: avgX, y: avgY } );
- }
-
- aggregatedData.push(aggregatedSeriesData);
- } );
-
- return aggregatedData;
- }
- } );
-
- this.setScale = function(scale) {
-
- if (scale < 1) {
- throw "scale out of range: " + scale;
- }
-
- this.aggregationScale = scale;
- this.graph.update();
- }
-};
-
-Rickshaw.namespace('Rickshaw.Graph.Unstacker');
-
-Rickshaw.Graph.Unstacker = function(args) {
-
- this.graph = args.graph;
- var self = this;
-
- this.graph.stackData.hooks.after.push( {
- name: 'unstacker',
- f: function(data) {
-
- if (!self.graph.renderer.unstack) return data;
-
- data.forEach( function(seriesData) {
- seriesData.forEach( function(d) {
- d.y0 = 0;
- } );
- } );
-
- return data;
- }
- } );
-};
-
-Rickshaw.namespace('Rickshaw.Series');
-
-Rickshaw.Series = Rickshaw.Class.create( Array, {
-
- initialize: function (data, palette, options) {
-
- options = options || {}
-
- this.palette = new Rickshaw.Color.Palette(palette);
-
- this.timeBase = typeof(options.timeBase) === 'undefined' ?
- Math.floor(new Date().getTime() / 1000) :
- options.timeBase;
-
- var timeInterval = typeof(options.timeInterval) == 'undefined' ?
- 1000 :
- options.timeInterval;
-
- this.setTimeInterval(timeInterval);
-
- if (data && (typeof(data) == "object") && (data instanceof Array)) {
- data.forEach( function(item) { this.addItem(item) }, this );
- }
- },
-
- addItem: function(item) {
-
- if (typeof(item.name) === 'undefined') {
- throw('addItem() needs a name');
- }
-
- item.color = (item.color || this.palette.color(item.name));
- item.data = (item.data || []);
-
- // backfill, if necessary
- if ((item.data.length == 0) && this.length && (this.getIndex() > 0)) {
- this[0].data.forEach( function(plot) {
- item.data.push({ x: plot.x, y: 0 });
- } );
- } else if (item.data.length == 0) {
- item.data.push({ x: this.timeBase - (this.timeInterval || 0), y: 0 });
- }
-
- this.push(item);
-
- if (this.legend) {
- this.legend.addLine(this.itemByName(item.name));
- }
- },
-
- addData: function(data) {
-
- var index = this.getIndex();
-
- Rickshaw.keys(data).forEach( function(name) {
- if (! this.itemByName(name)) {
- this.addItem({ name: name });
- }
- }, this );
-
- this.forEach( function(item) {
- item.data.push({
- x: (index * this.timeInterval || 1) + this.timeBase,
- y: (data[item.name] || 0)
- });
- }, this );
- },
-
- getIndex: function () {
- return (this[0] && this[0].data && this[0].data.length) ? this[0].data.length : 0;
- },
-
- itemByName: function(name) {
-
- for (var i = 0; i < this.length; i++) {
- if (this[i].name == name)
- return this[i];
- }
- },
-
- setTimeInterval: function(iv) {
- this.timeInterval = iv / 1000;
- },
-
- setTimeBase: function (t) {
- this.timeBase = t;
- },
-
- dump: function() {
-
- var data = {
- timeBase: this.timeBase,
- timeInterval: this.timeInterval,
- items: []
- };
-
- this.forEach( function(item) {
-
- var newItem = {
- color: item.color,
- name: item.name,
- data: []
- };
-
- item.data.forEach( function(plot) {
- newItem.data.push({ x: plot.x, y: plot.y });
- } );
-
- data.items.push(newItem);
- } );
-
- return data;
- },
-
- load: function(data) {
-
- if (data.timeInterval) {
- this.timeInterval = data.timeInterval;
- }
-
- if (data.timeBase) {
- this.timeBase = data.timeBase;
- }
-
- if (data.items) {
- data.items.forEach( function(item) {
- this.push(item);
- if (this.legend) {
- this.legend.addLine(this.itemByName(item.name));
- }
-
- }, this );
- }
- }
-} );
-
-Rickshaw.Series.zeroFill = function(series) {
- Rickshaw.Series.fill(series, 0);
-};
-
-Rickshaw.Series.fill = function(series, fill) {
-
- var x;
- var i = 0;
-
- var data = series.map( function(s) { return s.data } );
-
- while ( i < Math.max.apply(null, data.map( function(d) { return d.length } )) ) {
-
- x = Math.min.apply( null,
- data
- .filter(function(d) { return d[i] })
- .map(function(d) { return d[i].x })
- );
-
- data.forEach( function(d) {
- if (!d[i] || d[i].x != x) {
- d.splice(i, 0, { x: x, y: fill });
- }
- } );
-
- i++;
- }
-};
-
-Rickshaw.namespace('Rickshaw.Series.FixedDuration');
-
-Rickshaw.Series.FixedDuration = Rickshaw.Class.create(Rickshaw.Series, {
-
- initialize: function (data, palette, options) {
-
- var options = options || {}
-
- if (typeof(options.timeInterval) === 'undefined') {
- throw new Error('FixedDuration series requires timeInterval');
- }
-
- if (typeof(options.maxDataPoints) === 'undefined') {
- throw new Error('FixedDuration series requires maxDataPoints');
- }
-
- this.palette = new Rickshaw.Color.Palette(palette);
- this.timeBase = typeof(options.timeBase) === 'undefined' ? Math.floor(new Date().getTime() / 1000) : options.timeBase;
- this.setTimeInterval(options.timeInterval);
-
- if (this[0] && this[0].data && this[0].data.length) {
- this.currentSize = this[0].data.length;
- this.currentIndex = this[0].data.length;
- } else {
- this.currentSize = 0;
- this.currentIndex = 0;
- }
-
- this.maxDataPoints = options.maxDataPoints;
-
-
- if (data && (typeof(data) == "object") && (data instanceof Array)) {
- data.forEach( function (item) { this.addItem(item) }, this );
- this.currentSize += 1;
- this.currentIndex += 1;
- }
-
- // reset timeBase for zero-filled values if needed
- this.timeBase -= (this.maxDataPoints - this.currentSize) * this.timeInterval;
-
- // zero-fill up to maxDataPoints size if we don't have that much data yet
- if ((typeof(this.maxDataPoints) !== 'undefined') && (this.currentSize < this.maxDataPoints)) {
- for (var i = this.maxDataPoints - this.currentSize - 1; i > 0; i--) {
- this.currentSize += 1;
- this.currentIndex += 1;
- this.forEach( function (item) {
- item.data.unshift({ x: ((i-1) * this.timeInterval || 1) + this.timeBase, y: 0, i: i });
- }, this );
- }
- }
- },
-
- addData: function($super, data) {
-
- $super(data)
-
- this.currentSize += 1;
- this.currentIndex += 1;
-
- if (this.maxDataPoints !== undefined) {
- while (this.currentSize > this.maxDataPoints) {
- this.dropData();
- }
- }
- },
-
- dropData: function() {
-
- this.forEach(function(item) {
- item.data.splice(0, 1);
- } );
-
- this.currentSize -= 1;
- },
-
- getIndex: function () {
- return this.currentIndex;
- }
-} );
diff --git a/kitsune/sumo/static/sumo/js/questions.metrics-dashboard.js b/kitsune/sumo/static/sumo/js/questions.metrics-dashboard.js
deleted file mode 100644
index ad3fea32079..00000000000
--- a/kitsune/sumo/static/sumo/js/questions.metrics-dashboard.js
+++ /dev/null
@@ -1,143 +0,0 @@
-import "jquery-ui/ui/widgets/datepicker";
-import { Graph } from "sumo/js/rickshaw_utils";
-
-(function() {
-
- function init() {
- makeTopicsGraph();
- makeMetricsGraph();
- }
-
- function makeTopicsGraph() {
- var $topics, datums, seriesSpec, key;
-
- $('input[type=date]').attr('type','text').datepicker({
- dateFormat: 'yy-mm-dd'
- });
-
- $topics = $('#topic-stats');
- if ($topics.length === 0) {
- return;
- }
-
- datums = $topics.data('graph');
- seriesSpec = [];
-
- for (key in datums[0]) {
- if (key === 'date' || !datums[0].hasOwnProperty(key)) {
- continue;
- }
- // TODO: these names should be localized.
- seriesSpec.push({
- name: key,
- slug: key,
- func: Graph.identity(key)
- });
- }
-
- new Graph($topics, {
- data: {
- datums: datums,
- seriesSpec: seriesSpec
- },
- graph: {
- renderer: 'bar',
- width: 690,
- unstack: false
- },
- options: {
- slider: false
- }
- }).render();
- }
-
- function makeMetricsGraph() {
- var $container = $('#questions-metrics');
- $.getJSON($container.data('url'), function(data) {
- // Fill in 0s so bucketing doesn't freak out...
- var objects = data.objects;
- objects.forEach(function(object) {
- object.questions = object.questions || 0;
- object.solved = object.solved || 0;
- object.responded_24 = object.responded_24 || 0;
- object.responded_72 = object.responded_72 || 0;
- });
-
- new Graph($container, {
- data: {
- datums: objects,
- seriesSpec: [
- {
- name: gettext('Questions'),
- slug: 'questions',
- func: Graph.identity('questions'),
- color: '#5d84b2',
- axisGroup: 'questions',
- area: true
- },
- {
- name: gettext('Solved'),
- slug: 'num_solved',
- func: Graph.identity('solved'),
- color: '#aa4643',
- axisGroup: 'questions',
- area: true
- },
- {
- name: gettext('% Solved'),
- slug: 'solved',
- func: Graph.fraction('solved', 'questions'),
- color: '#aa4643',
- axisGroup: 'percent',
- type: 'percent'
- },
- {
- name: gettext('Responded in 24 hours'),
- slug: 'num_responded_24',
- func: Graph.identity('responded_24'),
- color: '#89a54e',
- axisGroup: 'questions',
- area: true
- },
- {
- name: gettext('% Responded in 24 hours'),
- slug: 'responded_24',
- func: Graph.fraction('responded_24', 'questions'),
- color: '#89a54e',
- axisGroup: 'percent',
- type: 'percent'
- },
- {
- name: gettext('Responded in 72 hours'),
- slug: 'num_responded_72',
- func: Graph.identity('responded_72'),
- color: '#80699b',
- axisGroup: 'questions',
- area: true
- },
- {
- name: gettext('% Responded in 72 hours'),
- slug: 'responded_72',
- func: Graph.fraction('responded_72', 'questions'),
- color: '#80699b',
- axisGroup: 'percent',
- type: 'percent'
- }
- ]
- },
- options: {
- legend: 'mini',
- slider: true,
- bucket: true
- },
- graph: {
- width: 880,
- height: 300
- },
- }).render();
- });
- }
-
- $(init);
-
-})();
diff --git a/kitsune/sumo/static/sumo/js/rickshaw_utils.js b/kitsune/sumo/static/sumo/js/rickshaw_utils.js
deleted file mode 100644
index 7c0ae106e97..00000000000
--- a/kitsune/sumo/static/sumo/js/rickshaw_utils.js
+++ /dev/null
@@ -1,1324 +0,0 @@
-import "jquery-ui/ui/widgets/datepicker";
-import "jquery-ui/ui/widgets/slider";
-import _filter from "underscore/modules/filter";
-import _map from "underscore/modules/map";
-import _each from "underscore/modules/each";
-import Rickshaw from "./libs/rickshaw";
-import { dateFormat } from "sumo/js/main";
-import d3 from "d3";
-
-/* class Graph */
-export function Graph($elem, extra) {
- var defaults = {
- toRender: [],
- options: {
- bucket: false,
- daterange: true,
- hover: true,
- init: true,
- legend: true,
- sets: false,
- slider: true,
- timeline: false,
- xAxis: true,
- yAxis: true
- },
-
- data: {
- datums: [],
- seriesSpec: [],
-
- annotations: [],
- bucketed: []
- },
-
- metadata: {
- colors: {},
- sets: {},
- bucketMethods: {}
- },
-
- graph: {
- renderer: 'area',
- interpolation: 'linear',
- stroke: true,
- unstack: true
- },
- hover: {},
- yAxis: {},
-
- rickshaw: {},
- dom: {},
- axisGroups: {},
- d3: {
- axises: {}
- }
- };
-
- // true means do a deep merge.
- $.extend(true, this, defaults, extra);
-
- this.dom.elem = $elem;
-
- if (this.options.init) {
- this.init();
- }
-};
-
-(function () {
- 'use strict';
-
- Graph.prototype.init = function () {
- window.G = this;
- this.initBucketUI();
- this.initData();
- this.initGraph();
- this.initAxises();
- this.initSlider();
- this.initDateRange();
- this.initLegend();
- this.initSets();
- this.initTimeline();
- };
-
- Graph.prototype.initData = function () {
- var i, d;
- for (i = 0; i < this.data.datums.length; i += 1) {
- d = this.data.datums[i];
- d.date = Graph.toSeconds(d.date || d.created || d.start);
- d.created = undefined;
- d.start = undefined;
- }
-
- this.rebucket();
- };
-
- Graph.prototype.rebucket = function () {
- var buckets, bucketed, i, d, axisGroup, axis, series, name, date, chopLimit, now;
- buckets = {};
- bucketed = [];
-
- // Bucket data
- if (this.data.bucketSize) {
- for (i = 0; i < this.data.datums.length; i += 1) {
- // make a copy.
- d = $.extend({}, this.data.datums[i]);
- date = new Date(d.date * 1000);
-
- // NB: These are resilient to borders in months and years because
- // JS's Date has the neat property that
- // new Date(2013, 4, -1) === new Date(2013, 3, 29)
- // new Date(2013, 0, -60) === new Date(2012, 10, 1)
- // This might be the only nice thing about JS's Date.
- switch (this.data.bucketSize) {
- case 'day':
- // Get midnight of today (ie, the boundary between today and yesterday)
- d.date = new Date(date.getFullYear(), date.getMonth(), date.getDate());
- break;
- case 'week':
- // Get the most recent Sunday.
- d.date = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay());
- break;
- case 'month':
- // Get the first of this month.
- d.date = new Date(date.getFullYear(), date.getMonth(), 1);
- break;
- default:
- throw 'Unknown bucket size ' + this.data.bucketSize;
- }
- d.date = d.date / 1000;
-
- if (buckets[d.date] === undefined) {
- buckets[d.date] = [d];
- } else {
- buckets[d.date].push(d);
- }
- }
-
- bucketed = $.map(buckets, function (dList) {
- var out, key;
- out = $.extend({}, dList[0]);
-
- for (key in out) {
- if (out.hasOwnProperty(key) && key !== 'date') {
- for (i = 1; i < dList.length; i += 1) {
- out[key] += dList[i][key];
- }
- }
- }
-
- return out;
- });
-
- } else {
- bucketed = this.data.datums.slice();
- }
-
- /* Data points that are too near the present represent a UX problem.
- * The data in them is not representative of a full time period, so
- * they appear to be downward trending. `chopLimit` represents the
- * boundary of what is considered to be "too new". Bug #876912. */
- now = new Date();
- if (this.data.bucketSize === 'week') {
- // Get most recent Sunday.
- chopLimit = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
- } else if (this.data.bucketSize === 'month') {
- // Get the first of the current month.
- chopLimit = new Date(now.getFullYear(), now.getMonth(), 1);
- } else {
- // Get midnight of today (ie, the boundary between today and yesterday)
- chopLimit = new Date(now.getFullYear(), now.getMonth(), now.getDate());
- }
- bucketed = _filter(bucketed, function (datum) {
- return datum.date < chopLimit / 1000;
- });
-
- this.data.series = this.makeSeries(bucketed, this.data.seriesSpec);
-
- // Scale data based on axis groups
- this.axisGroups = {};
- for (i = 0; i < this.data.series.length; i += 1) {
- series = this.data.series[i];
- name = series.axisGroup;
- if (this.axisGroups[name] === undefined) {
- this.axisGroups[name] = {
- max: -Infinity
- };
- }
-
- // Only adjust the axis max if the series is enabled.
- if (!series.disabled) {
- this.axisGroups[name].max = Math.max(this.axisGroups[name].max, series.max);
- }
- }
-
- function mapHandler(point) {
- return {
- x: point.x,
- y: point.y / axisGroup.max
- };
- }
-
- for (i = 0; i < this.data.series.length; i += 1) {
- series = this.data.series[i];
- axisGroup = this.axisGroups[series.axisGroup];
- series.data = _map(series.data, mapHandler);
- series.scale = axisGroup.max;
- axis = this.d3.axises[series.axisGroup];
- if (axis) {
- axis.setScale(axisGroup.max);
- }
- }
- };
-
- /* Take an array of datums and make a set of named x/y series, suitable
- * for Rickshaw. Each series is generated by one of the key functions.
- *
- * `descriptors` is an array of objects that define a name, a slug, and
- * a function to calculate data. Each data function will be used as a
- * map function on the datum objects to generate a series.
- *
- * Each descriptor may also optionally contain:
- * color: The color to draw this series in. The default is to use a
- * color generated by rickshaw.
- * disabled: If true, this graph will not be drawn. The default is
- * false.
- * axisGroup: The name of the axisGroup this series belongs to.
- *
- * Output will have all the above values, as well as the maximum
- * values within the current graph window.
- */
- Graph.prototype.makeSeries = function (objects, descriptors) {
- var i;
- var series = [];
- var desc;
- var min, max, data;
- var windowMin, windowMax;
- var stroke, fill;
- var r, g, b;
- var palette = new Rickshaw.Color.Palette();
-
- if (this.rickshaw.graph) {
- windowMin = this.rickshaw.graph.window.xMin || -Infinity;
- windowMax = this.rickshaw.graph.window.xMax || +Infinity;
- } else {
- windowMin = -Infinity;
- windowMax = +Infinity;
- }
-
- function mapHandler(datum) {
- var val = desc.func(datum);
-
- if (isNaN(val) ) {
- val = 0;
- }
-
- if (windowMin <= datum.date && datum.date <= windowMax) {
- min = Math.min(min, val);
- max = Math.max(max, val);
- }
-
- return {x: datum.date, y: val};
- }
-
- function yFormatter(value) {
- return Math.floor(value * 100) + '%';
- }
-
- for (i = 0; i < descriptors.length; i += 1) {
- min = Infinity;
- max = -Infinity;
- desc = descriptors[i];
-
- data = _map(objects, mapHandler);
-
- if (max <= 0 || isNaN(max) || !isFinite(max)) {
- max = 1;
- }
-
- if (this.graph.renderer === 'area') {
- stroke = desc.color || palette.color(desc.name);
- if (desc.area) {
- r = parseInt(desc.color.slice(1, 3), 16);
- g = parseInt(desc.color.slice(3, 5), 16);
- b = parseInt(desc.color.slice(5, 7), 16);
- fill = interpolate('rgba(%s,%s,%s,0.5)', [r, g, b]);
- } else {
- fill = 'rgba(0, 0, 0, 0.0)';
- }
- } else {
- // This is a bar graph. 'fill' is really color.
- stroke = undefined;
- fill = desc.color;
- }
-
- series[i] = {
- name: desc.name,
- slug: desc.slug,
- disabled: desc.disabled || false,
- type: desc.type || 'value',
-
- stroke: stroke,
- color: fill,
- axisGroup: desc.axisGroup,
- min: min,
- max: max,
- data: data
- };
-
- if (series[i].type === 'percent') {
- series[i].yFormatter = yFormatter;
- }
- }
-
- // Rickshaw gets angry when its data isn't sorted.
- function sortCallback (v1, v2) {
- return v1.x - v2.x;
- }
-
- for (i = 0; i < descriptors.length; i += 1) {
- series[i].data.sort(sortCallback);
- }
-
- return series;
- };
-
- Graph.prototype.getGraphData = function () {
- var palette = new Rickshaw.Color.Palette();
- var series = new Rickshaw.Series(this.data.series, palette);
-
- series.active = function () {
- // filter by active.
- return $.map(this, function (s) {
- if (!s.disabled) {
- return s;
- }
- });
- };
-
- return series;
- };
-
- Graph.prototype.initBucketUI = function () {
- if (!this.options.bucket) { return; }
-
- var i;
- var bucketSizes = [
- {value: 'day', text: gettext('Daily')},
- {value: 'week', text: gettext('Weekly')},
- {value: 'month', text: gettext('Monthly')}
- ];
-
- var $bucket = $('')
- .appendTo(this.dom.elem.find('.inline-controls'));
- var $select = $('