Commit 3ebdf325 authored by Mathieu Rodic's avatar Mathieu Rodic

[FILES] added required static files for the corpus view

parent 7583a060
.dc-chart {
float: left;
}
.dc-chart rect.bar {
stroke: none;
fill: steelblue;
}
.dc-chart rect.bar:hover {
fill-opacity: .5;
}
.dc-chart rect.stack1 {
stroke: none;
fill: red;
}
.dc-chart rect.stack2 {
stroke: none;
fill: green;
}
.dc-chart rect.deselected {
stroke: none;
fill: #ccc;
}
.dc-chart .sub .bar {
stroke: none;
fill: #ccc;
}
.dc-chart .pie-slice {
fill: white;
font-size: 12px;
cursor: pointer;
}
.dc-chart .pie-slice :hover{
fill-opacity: .8;
}
.dc-chart .selected path{
stroke-width: 3;
stroke: #ccc;
fill-opacity: 1;
}
.dc-chart .deselected path{
strok: none;
fill-opacity: .5;
fill: #ccc;
}
.dc-chart .axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dc-chart .axis text {
font: 10px sans-serif;
}
.dc-chart .grid-line line {
fill: none;
stroke: #ccc;
opacity: .5;
shape-rendering: crispEdges;
}
.dc-chart .brush rect.background {
z-index: -999;
}
.dc-chart .brush rect.extent {
fill: steelblue;
fill-opacity: .125;
}
.dc-chart .brush .resize path {
fill: #eee;
stroke: #666;
}
.dc-chart path.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.dc-chart circle.dot{
fill: steelblue;
}
.dc-chart g.stack1 path.line {
stroke: green;
}
.dc-chart g.stack1 circle.dot{
fill: green;
}
.dc-chart g.stack2 path.line {
stroke: red;
}
.dc-chart g.stack2 circle.dot{
fill: red;
}
.dc-chart g.dc-tooltip path{
fill: none;
stroke: grey;
stroke-opacity: .8;
}
.dc-chart path.area {
fill: steelblue;
fill-opacity: .3;
stroke: none;
}
.dc-chart g.stack1 path.area {
fill: green;
}
.dc-chart g.stack2 path.area {
fill: red;
}
.dc-chart .node {
font-size: 0.7em;
cursor: pointer;
}
.dc-chart .node :hover{
fill-opacity: .8;
}
.dc-chart .selected circle {
stroke-width: 3;
stroke: #ccc;
fill-opacity: 1;
}
.dc-chart .deselected circle {
strok: none;
fill-opacity: .5;
fill: #ccc;
}
.dc-chart .bubble {
stroke: none;
fill-opacity: 0.6;
}
.dc-data-count {
float: right;
margin-top: 15px;
margin-right: 15px;
}
.dc-data-count .filter-count {
color: #3182bd;
font-weight: bold;
}
.dc-data-count .total-count {
color: #3182bd;
font-weight: bold;
}
.dc-data-table {}
.dc-chart g.state{
cursor: pointer;
}
.dc-chart g.state :hover{
fill-opacity: .8;
}
.dc-chart g.state path {
stroke: white;
}
.dc-chart g.selected path {
}
.dc-chart g.deselected path {
fill: grey;
}
.dc-chart g.selected text {
}
.dc-chart g.deselected text {
display: none;
}
.dc-chart g.county path {
stroke: white;
fill: none;
}
.dc-chart g.debug rect{
fill: blue;
fill-opacity: .2;
}
.dc-chart g.row rect {
fill-opacity: 0.8;
cursor: pointer;
}
.dc-chart g.row rect:hover {
fill-opacity: 0.6;
}
.dc-chart g.row text {
fill: white;
font-size: 12px;
}
/*
* jQuery Dynatable plugin 0.3.1
*
* Copyright (c) 2014 Steve Schwartz (JangoSteve)
*
* Dual licensed under the AGPL and Proprietary licenses:
* http://www.dynatable.com/license/
*
* Date: Tue Jan 02 2014
*/
th {
background: #bd2525;
}
th a {
color: #fff;
}
th a:hover {
color: #fff;
text-decoration: underline;
}
.dynatable-search {
float: left;
margin-bottom: 10px;
}
.dynatable-pagination-links {
float: right;
}
.dynatable-record-count {
display: block;
padding: 5px 0;
}
.dynatable-pagination-links span,
.dynatable-pagination-links li {
display: inline-block;
}
.dynatable-page-link,
.dynatable-page-break {
display: block;
padding: 5px 7px;
}
.dynatable-page-link {
cursor: pointer;
}
.dynatable-active-page,
.dynatable-disabled-page {
cursor: text;
}
.dynatable-active-page:hover,
.dynatable-disabled-page:hover {
text-decoration: none;
}
.dynatable-active-page {
background: #bd2525;
border-radius: 5px;
color: #fff;
}
.dynatable-active-page:hover {
color: #fff;
}
.dynatable-disabled-page,
.dynatable-disabled-page:hover {
background: none;
color: #999;
}
.easyPieChart {
position: relative;
text-align: center;
}
.easyPieChart canvas {
position: absolute;
top: 0;
left: 0;
}
/* ==========================================================
* bootstrap-carousel.js v2.0.3
* http://twitter.github.com/bootstrap/javascript.html#carousel
* ==========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function ($) {
"use strict"; // jshint ;_;
/* CAROUSEL CLASS DEFINITION
* ========================= */
var Carousel = function (element, options) {
this.$element = $(element)
this.options = options
this.options.slide && this.slide(this.options.slide)
this.options.pause == 'hover' && this.$element
.on('mouseenter', $.proxy(this.pause, this))
.on('mouseleave', $.proxy(this.cycle, this))
}
Carousel.prototype = {
cycle: function (e) {
if (!e) this.paused = false
this.options.interval
&& !this.paused
&& (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
return this
}
, to: function (pos) {
var $active = this.$element.find('.active')
, children = $active.parent().children()
, activePos = children.index($active)
, that = this
if (pos > (children.length - 1) || pos < 0) return
if (this.sliding) {
return this.$element.one('slid', function () {
that.to(pos)
})
}
if (activePos == pos) {
return this.pause().cycle()
}
return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
}
, pause: function (e) {
if (!e) this.paused = true
clearInterval(this.interval)
this.interval = null
return this
}
, next: function () {
if (this.sliding) return
return this.slide('next')
}
, prev: function () {
if (this.sliding) return
return this.slide('prev')
}
, slide: function (type, next) {
var $active = this.$element.find('.active')
, $next = next || $active[type]()
, isCycling = this.interval
, direction = type == 'next' ? 'left' : 'right'
, fallback = type == 'next' ? 'first' : 'last'
, that = this
, e = $.Event('slide')
this.sliding = true
isCycling && this.pause()
$next = $next.length ? $next : this.$element.find('.item')[fallback]()
if ($next.hasClass('active')) return
if ($.support.transition && this.$element.hasClass('slide')) {
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
$next.addClass(type)
$next[0].offsetWidth // force reflow
$active.addClass(direction)
$next.addClass(direction)
this.$element.one($.support.transition.end, function () {
$next.removeClass([type, direction].join(' ')).addClass('active')
$active.removeClass(['active', direction].join(' '))
that.sliding = false
setTimeout(function () { that.$element.trigger('slid') }, 0)
})
} else {
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
$active.removeClass('active')
$next.addClass('active')
this.sliding = false
this.$element.trigger('slid')
}
isCycling && this.cycle()
return this
}
}
/* CAROUSEL PLUGIN DEFINITION
* ========================== */
$.fn.carousel = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('carousel')
, options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
if (!data) $this.data('carousel', (data = new Carousel(this, options)))
if (typeof option == 'number') data.to(option)
else if (typeof option == 'string' || (option = options.slide)) data[option]()
else if (options.interval) data.cycle()
})
}
$.fn.carousel.defaults = {
interval: 5000
, pause: 'hover'
}
$.fn.carousel.Constructor = Carousel
/* CAROUSEL DATA-API
* ================= */
$(function () {
$('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
var $this = $(this), href
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
, options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
$target.carousel(options)
e.preventDefault()
})
})
}(window.jQuery);
\ No newline at end of file
/* ===================================================
* bootstrap-transition.js v2.0.3
* http://twitter.github.com/bootstrap/javascript.html#transitions
* ===================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function ($) {
$(function () {
"use strict"; // jshint ;_;
/* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
* ======================================================= */
$.support.transition = (function () {
var transitionEnd = (function () {
var el = document.createElement('bootstrap')
, transEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd'
, 'MozTransition' : 'transitionend'
, 'OTransition' : 'oTransitionEnd'
, 'msTransition' : 'MSTransitionEnd'
, 'transition' : 'transitionend'
}
, name
for (name in transEndEventNames){
if (el.style[name] !== undefined) {
return transEndEventNames[name]
}
}
}())
return transitionEnd && {
end: transitionEnd
}
})()
})
}(window.jQuery);
\ No newline at end of file
(function(exports){
crossfilter.version = "1.1.1";
function crossfilter_identity(d) {
return d;
}
crossfilter.permute = permute;
function permute(array, index) {
for (var i = 0, n = index.length, copy = new Array(n); i < n; ++i) {
copy[i] = array[index[i]];
}
return copy;
}
var bisect = crossfilter.bisect = bisect_by(crossfilter_identity);
bisect.by = bisect_by;
function bisect_by(f) {
// Locate the insertion point for x in a to maintain sorted order. The
// arguments lo and hi may be used to specify a subset of the array which
// should be considered; by default the entire array is used. If x is already
// present in a, the insertion point will be before (to the left of) any
// existing entries. The return value is suitable for use as the first
// argument to `array.splice` assuming that a is already sorted.
// Incomparable values such as NaN and undefined are assumed to be at the end
// of the array.
//
// The returned insertion point i partitions the array a into two halves so
// that all v < x for v in a[lo:i] for the left side and all v >= x for v in
// a[i:hi] for the right side.
function bisectLeft(a, x, lo, hi) {
while (lo < hi) {
var mid = lo + hi >>> 1,
y = f(a[mid]);
if (x <= y || !(y <= y)) hi = mid;
else lo = mid + 1;
}
return lo;
}
// Similar to bisectLeft, but returns an insertion point which comes after (to
// the right of) any existing entries of x in a.
//
// The returned insertion point i partitions the array into two halves so that
// all v <= x for v in a[lo:i] for the left side and all v > x for v in
// a[i:hi] for the right side.
function bisectRight(a, x, lo, hi) {
while (lo < hi) {
var mid = lo + hi >>> 1,
y = f(a[mid]);
if (x < y || !(y <= y)) hi = mid;
else lo = mid + 1;
}
return lo;
}
bisectRight.right = bisectRight;
bisectRight.left = bisectLeft;
return bisectRight;
}
var heap = crossfilter.heap = heap_by(crossfilter_identity);
heap.by = heap_by;
function heap_by(f) {
// Builds a binary heap within the specified array a[lo:hi]. The heap has the
// property such that the parent a[lo+i] is always less than or equal to its
// two children: a[lo+2*i+1] and a[lo+2*i+2].
function heap(a, lo, hi) {
var n = hi - lo,
i = (n >>> 1) + 1;
while (--i > 0) sift(a, i, n, lo);
return a;
}
// Sorts the specified array a[lo:hi] in descending order, assuming it is
// already a heap.
function sort(a, lo, hi) {
var n = hi - lo,
t;
while (--n > 0) t = a[lo], a[lo] = a[lo + n], a[lo + n] = t, sift(a, 1, n, lo);
return a;
}
// Sifts the element a[lo+i-1] down the heap, where the heap is the contiguous
// slice of array a[lo:lo+n]. This method can also be used to update the heap
// incrementally, without incurring the full cost of reconstructing the heap.
function sift(a, i, n, lo) {
var d = a[--lo + i],
x = f(d),
child;
while ((child = i << 1) <= n) {
if (child < n && f(a[lo + child]) > f(a[lo + child + 1])) child++;
if (x <= f(a[lo + child])) break;
a[lo + i] = a[lo + child];
i = child;
}
a[lo + i] = d;
}
heap.sort = sort;
return heap;
}
var heapselect = crossfilter.heapselect = heapselect_by(crossfilter_identity);
heapselect.by = heapselect_by;
function heapselect_by(f) {
var heap = heap_by(f);
// Returns a new array containing the top k elements in the array a[lo:hi].
// The returned array is not sorted, but maintains the heap property. If k is
// greater than hi - lo, then fewer than k elements will be returned. The
// order of elements in a is unchanged by this operation.
function heapselect(a, lo, hi, k) {
var queue = new Array(k = Math.min(hi - lo, k)),
min,
i,
x,
d;
for (i = 0; i < k; ++i) queue[i] = a[lo++];
heap(queue, 0, k);
if (lo < hi) {
min = f(queue[0]);
do {
if (x = f(d = a[lo]) > min) {
queue[0] = d;
min = f(heap(queue, 0, k)[0]);
}
} while (++lo < hi);
}
return queue;
}
return heapselect;
}
var insertionsort = crossfilter.insertionsort = insertionsort_by(crossfilter_identity);
insertionsort.by = insertionsort_by;
function insertionsort_by(f) {
function insertionsort(a, lo, hi) {
for (var i = lo + 1; i < hi; ++i) {
for (var j = i, t = a[i], x = f(t), y; j > lo && ((y = f(a[j - 1])) > x || !(y <= y)); --j) {
a[j] = a[j - 1];
}
a[j] = t;
}
return a;
}
return insertionsort;
}
// Algorithm designed by Vladimir Yaroslavskiy.
// Implementation based on the Dart project; see lib/dart/LICENSE for details.
var quicksort = crossfilter.quicksort = quicksort_by(crossfilter_identity);
quicksort.by = quicksort_by;
function quicksort_by(f) {
var insertionsort = insertionsort_by(f);
function sort(a, lo, hi) {
return (hi - lo < quicksort_sizeThreshold
? insertionsort
: quicksort)(a, lo, hi);
}
function quicksort(a, lo, hi) {
// First move NaN and undefined to the end.
var x, y;
while (lo < hi && !((x = f(a[hi - 1])) <= x)) hi--;
for (var i = hi; --i >= lo; ) {
x = f(y = a[i]);
if (!(x <= x)) {
a[i] = a[--hi];
a[hi] = y;
}
}
// Compute the two pivots by looking at 5 elements.
var sixth = (hi - lo) / 6 | 0,
i1 = lo + sixth,
i5 = hi - 1 - sixth,
i3 = lo + hi - 1 >> 1, // The midpoint.
i2 = i3 - sixth,
i4 = i3 + sixth;
var e1 = a[i1], x1 = f(e1),
e2 = a[i2], x2 = f(e2),
e3 = a[i3], x3 = f(e3),
e4 = a[i4], x4 = f(e4),
e5 = a[i5], x5 = f(e5);
var t;
// Sort the selected 5 elements using a sorting network.
if (x1 > x2) t = e1, e1 = e2, e2 = t, t = x1, x1 = x2, x2 = t;
if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
if (x1 > x3) t = e1, e1 = e3, e3 = t, t = x1, x1 = x3, x3 = t;
if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
if (x1 > x4) t = e1, e1 = e4, e4 = t, t = x1, x1 = x4, x4 = t;
if (x3 > x4) t = e3, e3 = e4, e4 = t, t = x3, x3 = x4, x4 = t;
if (x2 > x5) t = e2, e2 = e5, e5 = t, t = x2, x2 = x5, x5 = t;
if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
var pivot1 = e2, pivotValue1 = x2,
pivot2 = e4, pivotValue2 = x4;
// e2 and e4 have been saved in the pivot variables. They will be written
// back, once the partitioning is finished.
a[i1] = e1;
a[i2] = a[lo];
a[i3] = e3;
a[i4] = a[hi - 1];
a[i5] = e5;
var less = lo + 1, // First element in the middle partition.
great = hi - 2; // Last element in the middle partition.
// Note that for value comparison, <, <=, >= and > coerce to a primitive via
// Object.prototype.valueOf; == and === do not, so in order to be consistent
// with natural order (such as for Date objects), we must do two compares.
var pivotsEqual = pivotValue1 <= pivotValue2 && pivotValue1 >= pivotValue2;
if (pivotsEqual) {
// Degenerated case where the partitioning becomes a dutch national flag
// problem.
//
// [ | < pivot | == pivot | unpartitioned | > pivot | ]
// ^ ^ ^ ^ ^
// left less k great right
//
// a[left] and a[right] are undefined and are filled after the
// partitioning.
//
// Invariants:
// 1) for x in ]left, less[ : x < pivot.
// 2) for x in [less, k[ : x == pivot.
// 3) for x in ]great, right[ : x > pivot.
for (var k = less; k <= great; ++k) {
var ek = a[k], xk = f(ek);
if (xk < pivotValue1) {
if (k !== less) {
a[k] = a[less];
a[less] = ek;
}
++less;
} else if (xk > pivotValue1) {
// Find the first element <= pivot in the range [k - 1, great] and
// put [:ek:] there. We know that such an element must exist:
// When k == less, then el3 (which is equal to pivot) lies in the
// interval. Otherwise a[k - 1] == pivot and the search stops at k-1.
// Note that in the latter case invariant 2 will be violated for a
// short amount of time. The invariant will be restored when the
// pivots are put into their final positions.
while (true) {
var greatValue = f(a[great]);
if (greatValue > pivotValue1) {
great--;
// This is the only location in the while-loop where a new
// iteration is started.
continue;
} else if (greatValue < pivotValue1) {
// Triple exchange.
a[k] = a[less];
a[less++] = a[great];
a[great--] = ek;
break;
} else {
a[k] = a[great];
a[great--] = ek;
// Note: if great < k then we will exit the outer loop and fix
// invariant 2 (which we just violated).
break;
}
}
}
}
} else {
// We partition the list into three parts:
// 1. < pivot1
// 2. >= pivot1 && <= pivot2
// 3. > pivot2
//
// During the loop we have:
// [ | < pivot1 | >= pivot1 && <= pivot2 | unpartitioned | > pivot2 | ]
// ^ ^ ^ ^ ^
// left less k great right
//
// a[left] and a[right] are undefined and are filled after the
// partitioning.
//
// Invariants:
// 1. for x in ]left, less[ : x < pivot1
// 2. for x in [less, k[ : pivot1 <= x && x <= pivot2
// 3. for x in ]great, right[ : x > pivot2
for (var k = less; k <= great; k++) {
var ek = a[k], xk = f(ek);
if (xk < pivotValue1) {
if (k !== less) {
a[k] = a[less];
a[less] = ek;
}
++less;
} else {
if (xk > pivotValue2) {
while (true) {
var greatValue = f(a[great]);
if (greatValue > pivotValue2) {
great--;
if (great < k) break;
// This is the only location inside the loop where a new
// iteration is started.
continue;
} else {
// a[great] <= pivot2.
if (greatValue < pivotValue1) {
// Triple exchange.
a[k] = a[less];
a[less++] = a[great];
a[great--] = ek;
} else {
// a[great] >= pivot1.
a[k] = a[great];
a[great--] = ek;
}
break;
}
}
}
}
}
}
// Move pivots into their final positions.
// We shrunk the list from both sides (a[left] and a[right] have
// meaningless values in them) and now we move elements from the first
// and third partition into these locations so that we can store the
// pivots.
a[lo] = a[less - 1];
a[less - 1] = pivot1;
a[hi - 1] = a[great + 1];
a[great + 1] = pivot2;
// The list is now partitioned into three partitions:
// [ < pivot1 | >= pivot1 && <= pivot2 | > pivot2 ]
// ^ ^ ^ ^
// left less great right
// Recursive descent. (Don't include the pivot values.)
sort(a, lo, less - 1);
sort(a, great + 2, hi);
if (pivotsEqual) {
// All elements in the second partition are equal to the pivot. No
// need to sort them.
return a;
}
// In theory it should be enough to call _doSort recursively on the second
// partition.
// The Android source however removes the pivot elements from the recursive
// call if the second partition is too large (more than 2/3 of the list).
if (less < i1 && great > i5) {
var lessValue, greatValue;
while ((lessValue = f(a[less])) <= pivotValue1 && lessValue >= pivotValue1) ++less;
while ((greatValue = f(a[great])) <= pivotValue2 && greatValue >= pivotValue2) --great;
// Copy paste of the previous 3-way partitioning with adaptions.
//
// We partition the list into three parts:
// 1. == pivot1
// 2. > pivot1 && < pivot2
// 3. == pivot2
//
// During the loop we have:
// [ == pivot1 | > pivot1 && < pivot2 | unpartitioned | == pivot2 ]
// ^ ^ ^
// less k great
//
// Invariants:
// 1. for x in [ *, less[ : x == pivot1
// 2. for x in [less, k[ : pivot1 < x && x < pivot2
// 3. for x in ]great, * ] : x == pivot2
for (var k = less; k <= great; k++) {
var ek = a[k], xk = f(ek);
if (xk <= pivotValue1 && xk >= pivotValue1) {
if (k !== less) {
a[k] = a[less];
a[less] = ek;
}
less++;
} else {
if (xk <= pivotValue2 && xk >= pivotValue2) {
while (true) {
var greatValue = f(a[great]);
if (greatValue <= pivotValue2 && greatValue >= pivotValue2) {
great--;
if (great < k) break;
// This is the only location inside the loop where a new
// iteration is started.
continue;
} else {
// a[great] < pivot2.
if (greatValue < pivotValue1) {
// Triple exchange.
a[k] = a[less];
a[less++] = a[great];
a[great--] = ek;
} else {
// a[great] == pivot1.
a[k] = a[great];
a[great--] = ek;
}
break;
}
}
}
}
}
}
// The second partition has now been cleared of pivot elements and looks
// as follows:
// [ * | > pivot1 && < pivot2 | * ]
// ^ ^
// less great
// Sort the second partition using recursive descent.
// The second partition looks as follows:
// [ * | >= pivot1 && <= pivot2 | * ]
// ^ ^
// less great
// Simply sort it by recursive descent.
return sort(a, less, great + 1);
}
return sort;
}
var quicksort_sizeThreshold = 32;
var crossfilter_array8 = crossfilter_arrayUntyped,
crossfilter_array16 = crossfilter_arrayUntyped,
crossfilter_array32 = crossfilter_arrayUntyped,
crossfilter_arrayLengthen = crossfilter_identity,
crossfilter_arrayWiden = crossfilter_identity;
if (typeof Uint8Array !== "undefined") {
crossfilter_array8 = function(n) { return new Uint8Array(n); };
crossfilter_array16 = function(n) { return new Uint16Array(n); };
crossfilter_array32 = function(n) { return new Uint32Array(n); };
crossfilter_arrayLengthen = function(array, length) {
var copy = new array.constructor(length);
copy.set(array);
return copy;
};
crossfilter_arrayWiden = function(array, width) {
var copy;
switch (width) {
case 16: copy = crossfilter_array16(array.length); break;
case 32: copy = crossfilter_array32(array.length); break;
default: throw new Error("invalid array width!");
}
copy.set(array);
return copy;
};
}
function crossfilter_arrayUntyped(n) {
return new Array(n);
}
function crossfilter_filterExact(bisect, value) {
return function(values) {
var n = values.length;
return [bisect.left(values, value, 0, n), bisect.right(values, value, 0, n)];
};
}
function crossfilter_filterRange(bisect, range) {
var min = range[0],
max = range[1];
return function(values) {
var n = values.length;
return [bisect.left(values, min, 0, n), bisect.left(values, max, 0, n)];
};
}
function crossfilter_filterAll(values) {
return [0, values.length];
}
function crossfilter_null() {
return null;
}
function crossfilter_zero() {
return 0;
}
function crossfilter_reduceIncrement(p) {
return p + 1;
}
function crossfilter_reduceDecrement(p) {
return p - 1;
}
function crossfilter_reduceAdd(f) {
return function(p, v) {
return p + +f(v);
};
}
function crossfilter_reduceSubtract(f) {
return function(p, v) {
return p - f(v);
};
}
exports.crossfilter = crossfilter;
function crossfilter() {
var crossfilter = {
add: add,
dimension: dimension,
groupAll: groupAll,
size: size
};
var data = [], // the records
n = 0, // the number of records; data.length
m = 0, // number of dimensions in use
M = 8, // number of dimensions that can fit in `filters`
filters = crossfilter_array8(0), // M bits per record; 1 is filtered out
filterListeners = [], // when the filters change
dataListeners = []; // when data is added
// Adds the specified new records to this crossfilter.
function add(newData) {
var n0 = n,
n1 = newData.length;
// If there's actually new data to add…
// Merge the new data into the existing data.
// Lengthen the filter bitset to handle the new records.
// Notify listeners (dimensions and groups) that new data is available.
if (n1) {
data = data.concat(newData);
filters = crossfilter_arrayLengthen(filters, n += n1);
dataListeners.forEach(function(l) { l(newData, n0, n1); });
}
return crossfilter;
}
// Adds a new dimension with the specified value accessor function.
function dimension(value) {
var dimension = {
filter: filter,
filterExact: filterExact,
filterRange: filterRange,
filterAll: filterAll,
top: top,
bottom: bottom,
group: group,
groupAll: groupAll
};
var one = 1 << m++, // bit mask, e.g., 00001000
zero = ~one, // inverted one, e.g., 11110111
values, // sorted, cached array
index, // value rank ↦ object id
newValues, // temporary array storing newly-added values
newIndex, // temporary array storing newly-added index
sort = quicksort_by(function(i) { return newValues[i]; }),
refilter = crossfilter_filterAll, // for recomputing filter
indexListeners = [], // when data is added
lo0 = 0,
hi0 = 0;
// Updating a dimension is a two-stage process. First, we must update the
// associated filters for the newly-added records. Once all dimensions have
// updated their filters, the groups are notified to update.
dataListeners.unshift(preAdd);
dataListeners.push(postAdd);
// Incorporate any existing data into this dimension, and make sure that the
// filter bitset is wide enough to handle the new dimension.
if (m > M) filters = crossfilter_arrayWiden(filters, M <<= 1);
preAdd(data, 0, n);
postAdd(data, 0, n);
// Incorporates the specified new records into this dimension.
// This function is responsible for updating filters, values, and index.
function preAdd(newData, n0, n1) {
// Permute new values into natural order using a sorted index.
newValues = newData.map(value);
newIndex = sort(crossfilter_range(n1), 0, n1);
newValues = permute(newValues, newIndex);
// Bisect newValues to determine which new records are selected.
var bounds = refilter(newValues), lo1 = bounds[0], hi1 = bounds[1], i;
for (i = 0; i < lo1; ++i) filters[newIndex[i] + n0] |= one;
for (i = hi1; i < n1; ++i) filters[newIndex[i] + n0] |= one;
// If this dimension previously had no data, then we don't need to do the
// more expensive merge operation; use the new values and index as-is.
if (!n0) {
values = newValues;
index = newIndex;
lo0 = lo1;
hi0 = hi1;
return;
}
var oldValues = values,
oldIndex = index,
i0 = 0,
i1 = 0;
// Otherwise, create new arrays into which to merge new and old.
values = new Array(n);
index = crossfilter_index(n, n);
// Merge the old and new sorted values, and old and new index.
for (i = 0; i0 < n0 && i1 < n1; ++i) {
if (oldValues[i0] < newValues[i1]) {
values[i] = oldValues[i0];
index[i] = oldIndex[i0++];
} else {
values[i] = newValues[i1];
index[i] = newIndex[i1++] + n0;
}
}
// Add any remaining old values.
for (; i0 < n0; ++i0, ++i) {
values[i] = oldValues[i0];
index[i] = oldIndex[i0];
}
// Add any remaining new values.
for (; i1 < n1; ++i1, ++i) {
values[i] = newValues[i1];
index[i] = newIndex[i1] + n0;
}
// Bisect again to recompute lo0 and hi0.
bounds = refilter(values), lo0 = bounds[0], hi0 = bounds[1];
}
// When all filters have updated, notify index listeners of the new values.
function postAdd(newData, n0, n1) {
indexListeners.forEach(function(l) { l(newValues, newIndex, n0, n1); });
newValues = newIndex = null;
}
// Updates the selected values based on the specified bounds [lo, hi].
// This implementation is used by all the public filter methods.
function filterIndex(bounds) {
var i,
j,
k,
lo1 = bounds[0],
hi1 = bounds[1],
added = [],
removed = [];
// Fast incremental update based on previous lo index.
if (lo1 < lo0) {
for (i = lo1, j = Math.min(lo0, hi1); i < j; ++i) {
filters[k = index[i]] ^= one;
added.push(k);
}
} else if (lo1 > lo0) {
for (i = lo0, j = Math.min(lo1, hi0); i < j; ++i) {
filters[k = index[i]] ^= one;
removed.push(k);
}
}
// Fast incremental update based on previous hi index.
if (hi1 > hi0) {
for (i = Math.max(lo1, hi0), j = hi1; i < j; ++i) {
filters[k = index[i]] ^= one;
added.push(k);
}
} else if (hi1 < hi0) {
for (i = Math.max(lo0, hi1), j = hi0; i < j; ++i) {
filters[k = index[i]] ^= one;
removed.push(k);
}
}
lo0 = lo1;
hi0 = hi1;
filterListeners.forEach(function(l) { l(one, added, removed); });
return dimension;
}
// Filters this dimension using the specified range, value, or null.
// If the range is null, this is equivalent to filterAll.
// If the range is an array, this is equivalent to filterRange.
// Otherwise, this is equivalent to filterExact.
function filter(range) {
return range == null
? filterAll() : Array.isArray(range)
? filterRange(range)
: filterExact(range);
}
// Filters this dimension to select the exact value.
function filterExact(value) {
return filterIndex((refilter = crossfilter_filterExact(bisect, value))(values));
}
// Filters this dimension to select the specified range [lo, hi].
// The lower bound is inclusive, and the upper bound is exclusive.
function filterRange(range) {
return filterIndex((refilter = crossfilter_filterRange(bisect, range))(values));
}
// Clears any filters on this dimension.
function filterAll() {
return filterIndex((refilter = crossfilter_filterAll)(values));
}
// Returns the top K selected records based on this dimension's order.
// Note: observes this dimension's filter, unlike group and groupAll.
function top(k) {
var array = [],
i = hi0,
j;
while (--i >= lo0 && k > 0) {
if (!filters[j = index[i]]) {
array.push(data[j]);
--k;
}
}
return array;
}
// Returns the bottom K selected records based on this dimension's order.
// Note: observes this dimension's filter, unlike group and groupAll.
function bottom(k) {
var array = [],
i = lo0,
j;
while (i < hi0 && k > 0) {
if (!filters[j = index[i]]) {
array.push(data[j]);
--k;
}
i++;
}
return array;
}
// Adds a new group to this dimension, using the specified key function.
function group(key) {
var group = {
top: top,
all: all,
reduce: reduce,
reduceCount: reduceCount,
reduceSum: reduceSum,
order: order,
orderNatural: orderNatural,
size: size
};
var groups, // array of {key, value}
groupIndex, // object id ↦ group id
groupWidth = 8,
groupCapacity = crossfilter_capacity(groupWidth),
k = 0, // cardinality
select,
heap,
reduceAdd,
reduceRemove,
reduceInitial,
update = crossfilter_null,
reset = crossfilter_null,
resetNeeded = true;
if (arguments.length < 1) key = crossfilter_identity;
// The group listens to the crossfilter for when any dimension changes, so
// that it can update the associated reduce values. It must also listen to
// the parent dimension for when data is added, and compute new keys.
filterListeners.push(update);
indexListeners.push(add);
// Incorporate any existing data into the grouping.
add(values, index, 0, n);
// Incorporates the specified new values into this group.
// This function is responsible for updating groups and groupIndex.
function add(newValues, newIndex, n0, n1) {
var oldGroups = groups,
reIndex = crossfilter_index(k, groupCapacity),
add = reduceAdd,
initial = reduceInitial,
k0 = k, // old cardinality
i0 = 0, // index of old group
i1 = 0, // index of new record
j, // object id
g0, // old group
x0, // old key
x1, // new key
g, // group to add
x; // key of group to add
// If a reset is needed, we don't need to update the reduce values.
if (resetNeeded) add = initial = crossfilter_null;
// Reset the new groups (k is a lower bound).
// Also, make sure that groupIndex exists and is long enough.
groups = new Array(k), k = 0;
groupIndex = k0 > 1 ? crossfilter_arrayLengthen(groupIndex, n) : crossfilter_index(n, groupCapacity);
// Get the first old key (x0 of g0), if it exists.
if (k0) x0 = (g0 = oldGroups[0]).key;
// Find the first new key (x1).
x1 = key(newValues[i1]);
// While new keys remain…
while (i1 < n1) {
// Determine the lesser of the two current keys; new and old.
// If there are no old keys remaining, then always add the new key.
if (g0 && x0 <= x1) {
g = g0, x = x0;
// Record the new index of the old group.
reIndex[i0] = k;
// Retrieve the next old key.
if (g0 = oldGroups[++i0]) x0 = g0.key;
} else {
g = {key: x1, value: initial()}, x = x1;
}
// Add the lesser group.
groups[k] = g;
// Add any selected records belonging to the added group, while
// advancing the new key and populating the associated group index.
while (x1 <= x || !(x1 <= x1) && !(x <= x)) {
groupIndex[j = newIndex[i1] + n0] = k;
if (!(filters[j] & zero)) g.value = add(g.value, data[j]);
if (++i1 >= n1) break;
x1 = key(newValues[i1]);
}
groupIncrement();
}
// Add any remaining old groups that were greater than all new keys.
// No incremental reduce is needed; these groups have no new records.
// Also record the new index of the old group.
while (i0 < k0) {
groups[reIndex[i0] = k] = oldGroups[i0++];
groupIncrement();
}
// If we added any new groups before any old groups,
// update the group index of all the old records.
if (k > i0) for (i0 = 0; i0 < n0; ++i0) {
groupIndex[i0] = reIndex[groupIndex[i0]];
}
// Modify the update and reset behavior based on the cardinality.
// If the cardinality is less than or equal to one, then the groupIndex
// is not needed. If the cardinality is zero, then there are no records
// and therefore no groups to update or reset. Note that we also must
// change the registered listener to point to the new method.
j = filterListeners.indexOf(update);
if (k > 1) {
update = updateMany;
reset = resetMany;
} else {
if (k === 1) {
update = updateOne;
reset = resetOne;
} else {
update = crossfilter_null;
reset = crossfilter_null;
}
groupIndex = null;
}
filterListeners[j] = update;
// Count the number of added groups,
// and widen the group index as needed.
function groupIncrement() {
if (++k === groupCapacity) {
reIndex = crossfilter_arrayWiden(reIndex, groupWidth <<= 1);
groupIndex = crossfilter_arrayWiden(groupIndex, groupWidth);
groupCapacity = crossfilter_capacity(groupWidth);
}
}
}
// Reduces the specified selected or deselected records.
// This function is only used when the cardinality is greater than 1.
function updateMany(filterOne, added, removed) {
if (filterOne === one || resetNeeded) return;
var i,
k,
n,
g;
// Add the added values.
for (i = 0, n = added.length; i < n; ++i) {
if (!(filters[k = added[i]] & zero)) {
g = groups[groupIndex[k]];
g.value = reduceAdd(g.value, data[k]);
}
}
// Remove the removed values.
for (i = 0, n = removed.length; i < n; ++i) {
if ((filters[k = removed[i]] & zero) === filterOne) {
g = groups[groupIndex[k]];
g.value = reduceRemove(g.value, data[k]);
}
}
}
// Reduces the specified selected or deselected records.
// This function is only used when the cardinality is 1.
function updateOne(filterOne, added, removed) {
if (filterOne === one || resetNeeded) return;
var i,
k,
n,
g = groups[0];
// Add the added values.
for (i = 0, n = added.length; i < n; ++i) {
if (!(filters[k = added[i]] & zero)) {
g.value = reduceAdd(g.value, data[k]);
}
}
// Remove the removed values.
for (i = 0, n = removed.length; i < n; ++i) {
if ((filters[k = removed[i]] & zero) === filterOne) {
g.value = reduceRemove(g.value, data[k]);
}
}
}
// Recomputes the group reduce values from scratch.
// This function is only used when the cardinality is greater than 1.
function resetMany() {
var i,
g;
// Reset all group values.
for (i = 0; i < k; ++i) {
groups[i].value = reduceInitial();
}
// Add any selected records.
for (i = 0; i < n; ++i) {
if (!(filters[i] & zero)) {
g = groups[groupIndex[i]];
g.value = reduceAdd(g.value, data[i]);
}
}
}
// Recomputes the group reduce values from scratch.
// This function is only used when the cardinality is 1.
function resetOne() {
var i,
g = groups[0];
// Reset the singleton group values.
g.value = reduceInitial();
// Add any selected records.
for (i = 0; i < n; ++i) {
if (!(filters[i] & zero)) {
g.value = reduceAdd(g.value, data[i]);
}
}
}
// Returns the array of group values, in the dimension's natural order.
function all() {
if (resetNeeded) reset(), resetNeeded = false;
return groups;
}
// Returns a new array containing the top K group values, in reduce order.
function top(k) {
var top = select(all(), 0, groups.length, k);
return heap.sort(top, 0, top.length);
}
// Sets the reduce behavior for this group to use the specified functions.
// This method lazily recomputes the reduce values, waiting until needed.
function reduce(add, remove, initial) {
reduceAdd = add;
reduceRemove = remove;
reduceInitial = initial;
resetNeeded = true;
return group;
}
// A convenience method for reducing by count.
function reduceCount() {
return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero);
}
// A convenience method for reducing by sum(value).
function reduceSum(value) {
return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero);
}
// Sets the reduce order, using the specified accessor.
function order(value) {
select = heapselect_by(valueOf);
heap = heap_by(valueOf);
function valueOf(d) { return value(d.value); }
return group;
}
// A convenience method for natural ordering by reduce value.
function orderNatural() {
return order(crossfilter_identity);
}
// Returns the cardinality of this group, irrespective of any filters.
function size() {
return k;
}
return reduceCount().orderNatural();
}
// A convenience function for generating a singleton group.
function groupAll() {
var g = group(crossfilter_null), all = g.all;
delete g.all;
delete g.top;
delete g.order;
delete g.orderNatural;
delete g.size;
g.value = function() { return all()[0].value; };
return g;
}
return dimension;
}
// A convenience method for groupAll on a dummy dimension.
// This implementation can be optimized since it is always cardinality 1.
function groupAll() {
var group = {
reduce: reduce,
reduceCount: reduceCount,
reduceSum: reduceSum,
value: value
};
var reduceValue,
reduceAdd,
reduceRemove,
reduceInitial,
resetNeeded = true;
// The group listens to the crossfilter for when any dimension changes, so
// that it can update the reduce value. It must also listen to the parent
// dimension for when data is added.
filterListeners.push(update);
dataListeners.push(add);
// For consistency; actually a no-op since resetNeeded is true.
add(data, 0, n);
// Incorporates the specified new values into this group.
function add(newData, n0) {
var i;
if (resetNeeded) return;
// Add the added values.
for (i = n0; i < n; ++i) {
if (!filters[i]) {
reduceValue = reduceAdd(reduceValue, data[i]);
}
}
}
// Reduces the specified selected or deselected records.
function update(filterOne, added, removed) {
var i,
k,
n;
if (resetNeeded) return;
// Add the added values.
for (i = 0, n = added.length; i < n; ++i) {
if (!filters[k = added[i]]) {
reduceValue = reduceAdd(reduceValue, data[k]);
}
}
// Remove the removed values.
for (i = 0, n = removed.length; i < n; ++i) {
if (filters[k = removed[i]] === filterOne) {
reduceValue = reduceRemove(reduceValue, data[k]);
}
}
}
// Recomputes the group reduce value from scratch.
function reset() {
var i;
reduceValue = reduceInitial();
for (i = 0; i < n; ++i) {
if (!filters[i]) {
reduceValue = reduceAdd(reduceValue, data[i]);
}
}
}
// Sets the reduce behavior for this group to use the specified functions.
// This method lazily recomputes the reduce value, waiting until needed.
function reduce(add, remove, initial) {
reduceAdd = add;
reduceRemove = remove;
reduceInitial = initial;
resetNeeded = true;
return group;
}
// A convenience method for reducing by count.
function reduceCount() {
return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero);
}
// A convenience method for reducing by sum(value).
function reduceSum(value) {
return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero);
}
// Returns the computed reduce value.
function value() {
if (resetNeeded) reset(), resetNeeded = false;
return reduceValue;
}
return reduceCount();
}
// Returns the number of records in this crossfilter, irrespective of any filters.
function size() {
return n;
}
return arguments.length
? add(arguments[0])
: crossfilter;
}
// Returns an array of size n, big enough to store ids up to m.
function crossfilter_index(n, m) {
return (m < 0x101
? crossfilter_array8 : m < 0x10001
? crossfilter_array16
: crossfilter_array32)(n);
}
// Constructs a new array of size n, with sequential values from 0 to n - 1.
function crossfilter_range(n) {
var range = crossfilter_index(n, n);
for (var i = -1; ++i < n;) range[i] = i;
return range;
}
function crossfilter_capacity(w) {
return w === 8
? 0x100 : w === 16
? 0x10000
: 0x100000000;
}
})(this);
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* Copyright 2012 the original author or authors.
* Licensed under the Apache License, Version 2.0 (the "License");
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
dc = {
version: "1.3.0",
constants : {
CHART_CLASS: "dc-chart",
DEBUG_GROUP_CLASS: "debug",
STACK_CLASS: "stack",
DESELECTED_CLASS: "deselected",
SELECTED_CLASS: "selected",
NODE_INDEX_NAME: "__index__",
GROUP_INDEX_NAME: "__group_index__",
DEFAULT_CHART_GROUP: "__default_chart_group__",
EVENT_DELAY: 40,
NEGLIGIBLE_NUMBER: 1e-10
},
_renderlet : null
};
dc.chartRegistry = function() {
// chartGroup:string => charts:array
var _chartMap = {};
this.has = function(chart) {
for (var e in _chartMap) {
if (_chartMap[e].indexOf(chart) >= 0)
return true;
}
return false;
};
function initializeChartGroup(group) {
if (!group)
group = dc.constants.DEFAULT_CHART_GROUP;
if (!_chartMap[group])
_chartMap[group] = [];
return group;
}
this.register = function(chart, group) {
group = initializeChartGroup(group);
_chartMap[group].push(chart);
};
this.clear = function() {
_chartMap = {};
};
this.list = function(group) {
group = initializeChartGroup(group);
return _chartMap[group];
};
return this;
}();
dc.registerChart = function(chart, group) {
dc.chartRegistry.register(chart, group);
};
dc.hasChart = function(chart) {
return dc.chartRegistry.has(chart);
};
dc.deregisterAllCharts = function() {
dc.chartRegistry.clear();
};
dc.filterAll = function(group) {
var charts = dc.chartRegistry.list(group);
for (var i = 0; i < charts.length; ++i) {
charts[i].filterAll();
}
};
dc.renderAll = function(group) {
var charts = dc.chartRegistry.list(group);
for (var i = 0; i < charts.length; ++i) {
charts[i].render();
}
if(dc._renderlet !== null)
dc._renderlet(group);
};
dc.redrawAll = function(group) {
var charts = dc.chartRegistry.list(group);
for (var i = 0; i < charts.length; ++i) {
charts[i].redraw();
}
if(dc._renderlet !== null)
dc._renderlet(group);
};
dc.transition = function(selections, duration, callback) {
if (duration <= 0 || duration === undefined)
return selections;
var s = selections
.transition()
.duration(duration);
if (callback instanceof Function) {
callback(s);
}
return s;
};
dc.units = {};
dc.units.integers = function(s, e) {
return Math.abs(e - s);
};
dc.units.ordinal = function(s, e, domain){
return domain;
};
dc.units.float = {};
dc.units.float.precision= function(precision){
var _f = function(s, e, domain){return Math.ceil(Math.abs((e-s)/_f.resolution));};
_f.resolution = precision;
return _f;
};
dc.round = {};
dc.round.floor = function(n) {
return Math.floor(n);
};
dc.round.ceil = function(n) {
return Math.ceil(n);
};
dc.round.round = function(n) {
return Math.round(n);
};
dc.override = function(obj, functionName, newFunction) {
var existingFunction = obj[functionName];
obj["_" + functionName] = existingFunction;
obj[functionName] = newFunction;
};
dc.renderlet = function(_){
if(!arguments.length) return dc._renderlet;
dc._renderlet = _;
return dc;
};
dc.instanceOfChart = function (o) {
return o instanceof Object && o.__dc_flag__;
};
dc.errors = {};
dc.errors.Exception = function(msg) {
var _msg = msg != null ? msg : "Unexpected internal error";
this.message = _msg;
this.toString = function(){
return _msg;
};
};
dc.errors.InvalidStateException = function() {
dc.errors.Exception.apply(this, arguments);
};dc.dateFormat = d3.time.format("%m/%d/%Y");
dc.printers = {};
dc.printers.filter = function (filter) {
var s = "";
if (filter) {
if (filter instanceof Array) {
if (filter.length >= 2)
s = "[" + printSingleValue(filter[0]) + " -> " + printSingleValue(filter[1]) + "]";
else if (filter.length >= 1)
s = printSingleValue(filter[0]);
} else {
s = printSingleValue(filter)
}
}
return s;
};
function printSingleValue(filter) {
var s = "" + filter;
if (filter instanceof Date)
s = dc.dateFormat(filter);
else if (typeof(filter) == "string")
s = filter;
else if (typeof(filter) == "number")
s = Math.round(filter);
return s;
}
dc.utils = {};
dc.utils.add = function (l, r) {
if (typeof r === "string")
r = r.replace("%", "")
if (l instanceof Date) {
if (typeof r === "string") r = +r
var d = new Date();
d.setTime(l.getTime());
d.setDate(l.getDate() + r);
return d;
} else if (typeof r === "string") {
var percentage = (+r / 100);
return l > 0 ? l * (1 + percentage) : l * (1 - percentage);
} else {
return l + r;
}
};
dc.utils.subtract = function (l, r) {
if (typeof r === "string")
r = r.replace("%", "")
if (l instanceof Date) {
if (typeof r === "string") r = +r
var d = new Date();
d.setTime(l.getTime());
d.setDate(l.getDate() - r);
return d;
} else if (typeof r === "string") {
var percentage = (+r / 100);
return l < 0 ? l * (1 + percentage) : l * (1 - percentage);
} else {
return l - r;
}
};
dc.utils.GroupStack = function () {
var _dataPointMatrix = [];
var _groups = [];
var _defaultAccessor;
function initializeDataPointRow(x) {
if (!_dataPointMatrix[x])
_dataPointMatrix[x] = [];
}
this.setDataPoint = function (x, y, data) {
initializeDataPointRow(x);
_dataPointMatrix[x][y] = data;
};
this.getDataPoint = function (x, y) {
initializeDataPointRow(x);
var dataPoint = _dataPointMatrix[x][y];
if (dataPoint == undefined)
dataPoint = 0;
return dataPoint;
};
this.addGroup = function (group, retriever) {
if (!retriever)
retriever = _defaultAccessor;
_groups.push([group, retriever]);
return _groups.length - 1;
};
this.getGroupByIndex = function (index) {
return _groups[index][0];
};
this.getAccessorByIndex = function (index) {
return _groups[index][1];
};
this.size = function () {
return _groups.length;
};
this.clear = function () {
_dataPointMatrix = [];
_groups = [];
};
this.setDefaultAccessor = function (retriever) {
_defaultAccessor = retriever;
};
this.getDataPoints = function () {
return _dataPointMatrix;
};
};
function isNegligible(max) {
return max === undefined || (max < dc.constants.NEGLIGIBLE_NUMBER && max > -dc.constants.NEGLIGIBLE_NUMBER);
}
dc.utils.groupMax = function (group, accessor) {
var max = d3.max(group.all(), function (e) {
return accessor(e);
});
if (isNegligible(max)) max = 0;
return max;
};
dc.utils.groupMin = function (group, accessor) {
var min = d3.min(group.all(), function (e) {
return accessor(e);
});
if (isNegligible(min)) min = 0;
return min;
};
dc.utils.nameToId = function (name) {
return name.toLowerCase().replace(/[\s]/g, "_").replace(/[\.']/g, "");
};
dc.utils.appendOrSelect = function (parent, name) {
var element = parent.select(name);
if (element.empty()) element = parent.append(name);
return element;
};
dc.events = {
current: null
};
dc.events.trigger = function(closure, delay) {
if (!delay){
closure();
return;
}
dc.events.current = closure;
setTimeout(function() {
if (closure == dc.events.current)
closure();
}, delay);
};
dc.cumulative = {};
dc.cumulative.Base = function() {
this._keyIndex = [];
this._map = {};
this.sanitizeKey = function(key) {
key = key + "";
return key;
};
this.clear = function() {
this._keyIndex = [];
this._map = {};
};
this.size = function() {
return this._keyIndex.length;
};
this.getValueByKey = function(key) {
key = this.sanitizeKey(key);
var value = this._map[key];
return value;
};
this.setValueByKey = function(key, value) {
key = this.sanitizeKey(key);
return this._map[key] = value;
};
this.indexOfKey = function(key) {
key = this.sanitizeKey(key);
return this._keyIndex.indexOf(key);
};
this.addToIndex = function(key) {
key = this.sanitizeKey(key);
this._keyIndex.push(key);
};
this.getKeyByIndex = function(index) {
return this._keyIndex[index];
};
};
dc.cumulative.Sum = function() {
dc.cumulative.Base.apply(this, arguments);
this.add = function(key, value) {
if (value == null)
value = 0;
if (this.getValueByKey(key) == null) {
this.addToIndex(key);
this.setValueByKey(key, value);
} else {
this.setValueByKey(key, this.getValueByKey(key) + value);
}
};
this.minus = function(key, value) {
this.setValueByKey(key, this.getValueByKey(key) - value);
};
this.cumulativeSum = function(key) {
var keyIndex = this.indexOfKey(key);
if (keyIndex < 0) return 0;
var cumulativeValue = 0;
for (var i = 0; i <= keyIndex; ++i) {
var k = this.getKeyByIndex(i);
cumulativeValue += this.getValueByKey(k);
}
return cumulativeValue;
};
};
dc.cumulative.Sum.prototype = new dc.cumulative.Base();
dc.cumulative.CountUnique = function() {
dc.cumulative.Base.apply(this, arguments);
function hashSize(hash) {
var size = 0, key;
for (key in hash) {
if (hash.hasOwnProperty(key)) size++;
}
return size;
}
this.add = function(key, e) {
if (this.getValueByKey(key) == null) {
this.setValueByKey(key, {});
this.addToIndex(key);
}
if (e != null) {
if (this.getValueByKey(key)[e] == null)
this.getValueByKey(key)[e] = 0;
this.getValueByKey(key)[e] += 1;
}
};
this.minus = function(key, e) {
this.getValueByKey(key)[e] -= 1;
if (this.getValueByKey(key)[e] <= 0)
delete this.getValueByKey(key)[e];
};
this.count = function(key) {
return hashSize(this.getValueByKey(key));
};
this.cumulativeCount = function(key) {
var keyIndex = this.indexOfKey(key);
if (keyIndex < 0) return 0;
var cumulativeCount = 0;
for (var i = 0; i <= keyIndex; ++i) {
var k = this.getKeyByIndex(i);
cumulativeCount += this.count(k);
}
return cumulativeCount;
};
};
dc.cumulative.CountUnique.prototype = new dc.cumulative.Base();
dc.baseChart = function (_chart) {
_chart.__dc_flag__ = true;
var _dimension;
var _group;
var _anchor;
var _root;
var _svg;
var _width = 200, _height = 200;
var _keyAccessor = function (d) {
return d.key;
};
var _valueAccessor = function (d) {
return d.value;
};
var _label = function (d) {
return d.key;
};
var _renderLabel = false;
var _title = function (d) {
return d.key + ": " + d.value;
};
var _renderTitle = false;
var _transitionDuration = 750;
var _filterPrinter = dc.printers.filter;
var _renderlets = [];
var _chartGroup = dc.constants.DEFAULT_CHART_GROUP;
var NULL_LISTENER = function (chart) {
};
var _listeners = {
preRender: NULL_LISTENER,
postRender: NULL_LISTENER,
preRedraw: NULL_LISTENER,
postRedraw: NULL_LISTENER,
filtered: NULL_LISTENER
};
_chart.width = function (w) {
if (!arguments.length) return _width;
_width = w;
return _chart;
};
_chart.height = function (h) {
if (!arguments.length) return _height;
_height = h;
return _chart;
};
_chart.dimension = function (d) {
if (!arguments.length) return _dimension;
_dimension = d;
_chart.expireCache();
return _chart;
};
_chart.group = function (g) {
if (!arguments.length) return _group;
_group = g;
_chart.expireCache();
return _chart;
};
_chart.orderedGroup = function () {
return _group.order(function (p) {
return p.key;
});
};
_chart.filterAll = function () {
return _chart.filter(null);
};
_chart.dataSet = function () {
return _dimension != undefined && _group != undefined;
};
_chart.select = function (s) {
return _root.select(s);
};
_chart.selectAll = function (s) {
return _root.selectAll(s);
};
_chart.anchor = function (a, chartGroup) {
if (!arguments.length) return _anchor;
if (dc.instanceOfChart(a)) {
_anchor = a.anchor();
_root = a.root();
} else {
_anchor = a;
_root = d3.select(_anchor);
_root.classed(dc.constants.CHART_CLASS, true);
dc.registerChart(_chart, chartGroup);
}
_chartGroup = chartGroup;
return _chart;
};
_chart.root = function (r) {
if (!arguments.length) return _root;
_root = r;
return _chart;
};
_chart.svg = function (_) {
if (!arguments.length) return _svg;
_svg = _;
return _chart;
};
_chart.resetSvg = function () {
_chart.select("svg").remove();
return _chart.generateSvg();
};
_chart.generateSvg = function () {
_svg = _chart.root().append("svg")
.attr("width", _chart.width())
.attr("height", _chart.height());
return _svg;
};
_chart.filterPrinter = function (_) {
if (!arguments.length) return _filterPrinter;
_filterPrinter = _;
return _chart;
};
_chart.turnOnControls = function () {
_chart.selectAll(".reset").style("display", null);
_chart.selectAll(".filter").text(_filterPrinter(_chart.filter())).style("display", null);
return _chart;
};
_chart.turnOffControls = function () {
_chart.selectAll(".reset").style("display", "none");
_chart.selectAll(".filter").style("display", "none").text(_chart.filter());
return _chart;
};
_chart.transitionDuration = function (d) {
if (!arguments.length) return _transitionDuration;
_transitionDuration = d;
return _chart;
};
_chart.render = function () {
_listeners.preRender(_chart);
if (_dimension == null)
throw new dc.errors.InvalidStateException("Mandatory attribute chart.dimension is missing on chart["
+ _chart.anchor() + "]");
if (_group == null)
throw new dc.errors.InvalidStateException("Mandatory attribute chart.group is missing on chart["
+ _chart.anchor() + "]");
var result = _chart.doRender();
if (_chart.transitionDuration() > 0) {
setTimeout(function () {
_chart.invokeRenderlet(_chart);
_listeners.postRender(_chart);
}, _chart.transitionDuration());
} else {
_chart.invokeRenderlet(_chart);
_listeners.postRender(_chart);
}
return result;
};
_chart.redraw = function () {
_listeners.preRedraw(_chart);
var result = _chart.doRedraw();
_chart.invokeRenderlet(_chart);
_listeners.postRedraw(_chart);
return result;
};
_chart.invokeFilteredListener = function (chart, f) {
if (f !== undefined) _listeners.filtered(_chart, f);
};
// abstract function stub
_chart.filter = function (f) {
// do nothing in base, should be overridden by sub-function
_chart.invokeFilteredListener(_chart, f);
return _chart;
};
_chart.doRender = function () {
// do nothing in base, should be overridden by sub-function
return _chart;
};
_chart.doRedraw = function () {
// do nothing in base, should be overridden by sub-function
return _chart;
};
_chart.keyAccessor = function (_) {
if (!arguments.length) return _keyAccessor;
_keyAccessor = _;
return _chart;
};
_chart.valueAccessor = function (_) {
if (!arguments.length) return _valueAccessor;
_valueAccessor = _;
return _chart;
};
_chart.label = function (_) {
if (!arguments.length) return _label;
_label = _;
_renderLabel = true;
return _chart;
};
_chart.renderLabel = function (_) {
if (!arguments.length) return _renderLabel;
_renderLabel = _;
return _chart;
};
_chart.title = function (_) {
if (!arguments.length) return _title;
_title = _;
_renderTitle = true;
return _chart;
};
_chart.renderTitle = function (_) {
if (!arguments.length) return _renderTitle;
_renderTitle = _;
return _chart;
};
_chart.renderlet = function (_) {
_renderlets.push(_);
return _chart;
};
_chart.invokeRenderlet = function (chart) {
for (var i = 0; i < _renderlets.length; ++i) {
_renderlets[i](chart);
}
};
_chart.chartGroup = function (_) {
if (!arguments.length) return _chartGroup;
_chartGroup = _;
return _chart;
};
_chart.on = function (event, listener) {
_listeners[event] = listener;
return _chart;
};
_chart.expireCache = function(){
// do nothing in base, should be overridden by sub-function
return _chart;
};
return _chart;
};
dc.marginable = function (_chart) {
var _margin = {top: 10, right: 50, bottom: 30, left: 30};
_chart.margins = function (m) {
if (!arguments.length) return _margin;
_margin = m;
return _chart;
};
_chart.effectiveWidth = function () {
return _chart.width() - _chart.margins().left - _chart.margins().right;
};
_chart.effectiveHeight = function () {
return _chart.height() - _chart.margins().top - _chart.margins().bottom;
};
return _chart;
};dc.coordinateGridChart = function (_chart) {
var DEFAULT_Y_AXIS_TICKS = 5;
var GRID_LINE_CLASS = "grid-line";
var HORIZONTAL_CLASS = "horizontal";
var VERTICAL_CLASS = "vertical";
_chart = dc.marginable(dc.baseChart(_chart));
var _parent;
var _g;
var _chartBodyG;
var _x;
var _xOriginalDomain;
var _xAxis = d3.svg.axis();
var _xUnits = dc.units.integers;
var _xAxisPadding = 0;
var _xElasticity = false;
var _y;
var _yAxis = d3.svg.axis();
var _yAxisPadding = 0;
var _yElasticity = false;
var _filter;
var _brush = d3.svg.brush();
var _brushOn = true;
var _round;
var _renderHorizontalGridLine = false;
var _renderVerticalGridLine = false;
var _refocused = false;
var _unitCount;
_chart.generateG = function (parent) {
if (parent == null)
_parent = _chart.svg();
else
_parent = parent;
_g = _parent.append("g");
_chartBodyG = _g.append("g").attr("class", "chartBody")
.attr("clip-path", "url(#" + getClipPathId() + ")");
return _g;
};
_chart.g = function (_) {
if (!arguments.length) return _g;
_g = _;
return _chart;
};
_chart.chartBodyG = function (_) {
if (!arguments.length) return _chartBodyG;
_chartBodyG = _;
return _chart;
};
_chart.x = function (_) {
if (!arguments.length) return _x;
_x = _;
_xOriginalDomain = _x.domain();
return _chart;
};
_chart.xOriginalDomain = function () {
return _xOriginalDomain;
};
_chart.xUnits = function (_) {
if (!arguments.length) return _xUnits;
_xUnits = _;
return _chart;
};
_chart.xAxis = function (_) {
if (!arguments.length) return _xAxis;
_xAxis = _;
return _chart;
};
_chart.elasticX = function (_) {
if (!arguments.length) return _xElasticity;
_xElasticity = _;
return _chart;
};
_chart.xAxisPadding = function (_) {
if (!arguments.length) return _xAxisPadding;
_xAxisPadding = _;
return _chart;
};
_chart.xUnitCount = function () {
if (_unitCount == null || _chart.refocused()) {
var units = _chart.xUnits()(_chart.x().domain()[0], _chart.x().domain()[1], _chart.x().domain());
if (units instanceof Array)
_unitCount = units.length;
else
_unitCount = units;
}
return _unitCount;
};
_chart.isOrdinal = function () {
return _chart.xUnits() === dc.units.ordinal;
};
_chart.prepareOrdinalXAxis = function (count) {
if (!count)
count = _chart.xUnitCount();
var range = [];
var currentPosition = 0;
var increment = _chart.xAxisLength() / count;
for (var i = 0; i < count; i++) {
range[i] = currentPosition;
currentPosition += increment;
}
_x.range(range);
};
function prepareXAxis(g) {
if (_chart.elasticX() && !_chart.isOrdinal()) {
_x.domain([_chart.xAxisMin(), _chart.xAxisMax()]);
}
if (_chart.isOrdinal()) {
_chart.prepareOrdinalXAxis();
} else {
_x.range([0, _chart.xAxisLength()]);
}
_xAxis = _xAxis.scale(_chart.x()).orient("bottom");
renderVerticalGridLines(g);
}
_chart.renderXAxis = function (g) {
var axisXG = g.selectAll("g.x");
if (axisXG.empty())
axisXG = g.append("g")
.attr("class", "axis x")
.attr("transform", "translate(" + _chart.margins().left + "," + _chart.xAxisY() + ")");
dc.transition(axisXG, _chart.transitionDuration())
.call(_xAxis);
};
function renderVerticalGridLines(g) {
if (_renderVerticalGridLine) {
var gridLineG = g.selectAll("g." + VERTICAL_CLASS);
if (gridLineG.empty())
gridLineG = g.insert("g", ":first-child")
.attr("class", GRID_LINE_CLASS + " " + VERTICAL_CLASS)
.attr("transform", "translate(" + _chart.yAxisX() + "," + _chart.margins().top + ")");
var ticks = _xAxis.tickValues() ? _xAxis.tickValues() : _x.ticks(_xAxis.ticks()[0]);
var lines = gridLineG.selectAll("line")
.data(ticks);
// enter
var linesGEnter = lines.enter()
.append("line")
.attr("x1", function (d) {
return _x(d);
})
.attr("y1", _chart.xAxisY() - _chart.margins().top)
.attr("x2", function (d) {
return _x(d);
})
.attr("y2", 0)
.attr("opacity", 0);
dc.transition(linesGEnter, _chart.transitionDuration())
.attr("opacity", 1);
// update
dc.transition(lines, _chart.transitionDuration())
.attr("x1", function (d) {
return _x(d);
})
.attr("y1", _chart.xAxisY() - _chart.margins().top)
.attr("x2", function (d) {
return _x(d);
})
.attr("y2", 0);
// exit
lines.exit().remove();
}
}
_chart.xAxisY = function () {
return (_chart.height() - _chart.margins().bottom);
};
_chart.xAxisLength = function () {
return _chart.effectiveWidth();
};
function prepareYAxis(g) {
if (_y == null || _chart.elasticY()) {
_y = d3.scale.linear();
_y.domain([_chart.yAxisMin(), _chart.yAxisMax()]).rangeRound([_chart.yAxisHeight(), 0]);
}
_y.range([_chart.yAxisHeight(), 0]);
_yAxis = _yAxis.scale(_y).orient("left").ticks(DEFAULT_Y_AXIS_TICKS);
renderHorizontalGridLines(g);
}
_chart.renderYAxis = function (g) {
var axisYG = g.selectAll("g.y");
if (axisYG.empty())
axisYG = g.append("g")
.attr("class", "axis y")
.attr("transform", "translate(" + _chart.yAxisX() + "," + _chart.margins().top + ")");
dc.transition(axisYG, _chart.transitionDuration())
.call(_yAxis);
};
function renderHorizontalGridLines(g) {
if (_renderHorizontalGridLine) {
var gridLineG = g.selectAll("g." + HORIZONTAL_CLASS);
var ticks = _yAxis.tickValues() ? _yAxis.tickValues() : _y.ticks(_yAxis.ticks()[0]);
if (gridLineG.empty())
gridLineG = g.insert("g", ":first-child")
.attr("class", GRID_LINE_CLASS + " " + HORIZONTAL_CLASS)
.attr("transform", "translate(" + _chart.yAxisX() + "," + _chart.margins().top + ")");
var lines = gridLineG.selectAll("line")
.data(ticks);
// enter
var linesGEnter = lines.enter()
.append("line")
.attr("x1", 1)
.attr("y1", function (d) {
return _y(d);
})
.attr("x2", _chart.xAxisLength())
.attr("y2", function (d) {
return _y(d);
})
.attr("opacity", 0);
dc.transition(linesGEnter, _chart.transitionDuration())
.attr("opacity", 1);
// update
dc.transition(lines, _chart.transitionDuration())
.attr("x1", 1)
.attr("y1", function (d) {
return _y(d);
})
.attr("x2", _chart.xAxisLength())
.attr("y2", function (d) {
return _y(d);
});
// exit
lines.exit().remove();
}
}
_chart.yAxisX = function () {
return _chart.margins().left;
};
_chart.y = function (_) {
if (!arguments.length) return _y;
_y = _;
return _chart;
};
_chart.yAxis = function (y) {
if (!arguments.length) return _yAxis;
_yAxis = y;
return _chart;
};
_chart.elasticY = function (_) {
if (!arguments.length) return _yElasticity;
_yElasticity = _;
return _chart;
};
_chart.renderHorizontalGridLines = function (_) {
if (!arguments.length) return _renderHorizontalGridLine;
_renderHorizontalGridLine = _;
return _chart;
};
_chart.renderVerticalGridLines = function (_) {
if (!arguments.length) return _renderVerticalGridLine;
_renderVerticalGridLine = _;
return _chart;
};
_chart.xAxisMin = function () {
var min = d3.min(_chart.group().all(), function (e) {
return _chart.keyAccessor()(e);
});
return dc.utils.subtract(min, _xAxisPadding);
};
_chart.xAxisMax = function () {
var max = d3.max(_chart.group().all(), function (e) {
return _chart.keyAccessor()(e);
});
return dc.utils.add(max, _xAxisPadding);
};
_chart.yAxisMin = function () {
var min = d3.min(_chart.group().all(), function (e) {
return _chart.valueAccessor()(e);
});
min = dc.utils.subtract(min, _yAxisPadding);
return min;
};
_chart.yAxisMax = function () {
var max = d3.max(_chart.group().all(), function (e) {
return _chart.valueAccessor()(e);
});
max = dc.utils.add(max, _yAxisPadding);
return max;
};
_chart.yAxisPadding = function (_) {
if (!arguments.length) return _yAxisPadding;
_yAxisPadding = _;
return _chart;
};
_chart.yAxisHeight = function () {
return _chart.effectiveHeight();
};
_chart.round = function (_) {
if (!arguments.length) return _round;
_round = _;
return _chart;
};
_chart.filter = function (_) {
if (!arguments.length) return _filter;
if (_) {
_filter = _;
_chart.brush().extent(_);
_chart.dimension().filter(_);
_chart.turnOnControls();
} else {
_filter = null;
_chart.brush().clear();
_chart.dimension().filterAll();
_chart.turnOffControls();
}
_chart.invokeFilteredListener(_chart, _);
return _chart;
};
_chart.brush = function (_) {
if (!arguments.length) return _brush;
_brush = _;
return _chart;
};
function brushHeight() {
return _chart.xAxisY() - _chart.margins().top;
}
_chart.renderBrush = function (g) {
if (_chart.isOrdinal())
_brushOn = false;
if (_brushOn) {
_brush.on("brushstart", brushStart)
.on("brush", brushing)
.on("brushend", brushEnd);
var gBrush = g.append("g")
.attr("class", "brush")
.attr("transform", "translate(" + _chart.margins().left + "," + _chart.margins().top + ")")
.call(_brush.x(_chart.x()));
gBrush.selectAll("rect").attr("height", brushHeight());
gBrush.selectAll(".resize").append("path").attr("d", _chart.resizeHandlePath);
if (_filter) {
_chart.redrawBrush(g);
}
}
};
function brushStart(p) {
}
_chart.extendBrush = function () {
var extent = _brush.extent();
if (_chart.round()) {
extent[0] = extent.map(_chart.round())[0];
extent[1] = extent.map(_chart.round())[1];
_g.select(".brush")
.call(_brush.extent(extent));
}
return extent;
};
_chart.brushIsEmpty = function (extent) {
return _brush.empty() || !extent || extent[1] <= extent[0];
};
function brushing(p) {
var extent = _chart.extendBrush();
_chart.redrawBrush(_g);
if (_chart.brushIsEmpty(extent)) {
dc.events.trigger(function () {
_chart.filter(null);
dc.redrawAll(_chart.chartGroup());
});
} else {
dc.events.trigger(function () {
_chart.filter([extent[0], extent[1]]);
dc.redrawAll(_chart.chartGroup());
}, dc.constants.EVENT_DELAY);
}
}
function brushEnd(p) {
}
_chart.redrawBrush = function (g) {
if (_brushOn) {
if (_chart.filter() && _chart.brush().empty())
_chart.brush().extent(_chart.filter());
var gBrush = g.select("g.brush");
gBrush.call(_chart.brush().x(_chart.x()));
gBrush.selectAll("rect").attr("height", brushHeight());
}
_chart.fadeDeselectedArea();
};
_chart.fadeDeselectedArea = function () {
// do nothing, sub-chart should override this function
};
// borrowed from Crossfilter example
_chart.resizeHandlePath = function (d) {
var e = +(d == "e"), x = e ? 1 : -1, y = brushHeight() / 3;
return "M" + (.5 * x) + "," + y
+ "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6)
+ "V" + (2 * y - 6)
+ "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y)
+ "Z"
+ "M" + (2.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8)
+ "M" + (4.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8);
};
function getClipPathId() {
return _chart.anchor().replace('#', '') + "-clip";
}
function generateClipPath() {
var defs = dc.utils.appendOrSelect(_parent, "defs");
var chartBodyClip = dc.utils.appendOrSelect(defs, "clipPath").attr("id", getClipPathId());
dc.utils.appendOrSelect(chartBodyClip, "rect")
.attr("x", _chart.margins().left)
.attr("y", _chart.margins().top)
.attr("width", _chart.xAxisLength())
.attr("height", _chart.yAxisHeight());
}
_chart.doRender = function () {
if (_x == null)
throw new dc.errors.InvalidStateException("Mandatory attribute chart.x is missing on chart["
+ _chart.anchor() + "]");
_chart.resetSvg();
if (_chart.dataSet()) {
_chart.generateG();
generateClipPath();
prepareXAxis(_chart.g());
prepareYAxis(_chart.g());
_chart.plotData();
_chart.renderXAxis(_chart.g());
_chart.renderYAxis(_chart.g());
_chart.renderBrush(_chart.g());
}
return _chart;
};
_chart.doRedraw = function () {
prepareXAxis(_chart.g());
prepareYAxis(_chart.g());
_chart.plotData();
if (_chart.elasticY())
_chart.renderYAxis(_chart.g());
if (_chart.elasticX() || _refocused)
_chart.renderXAxis(_chart.g());
_chart.redrawBrush(_chart.g());
return _chart;
};
_chart.subRender = function () {
if (_chart.dataSet()) {
_chart.plotData();
}
return _chart;
};
_chart.brushOn = function (_) {
if (!arguments.length) return _brushOn;
_brushOn = _;
return _chart;
};
_chart.getDataWithinXDomain = function (group) {
var data = [];
if (_chart.isOrdinal()) {
data = group.all();
} else {
group.all().forEach(function (d) {
var key = _chart.keyAccessor()(d);
if (key >= _chart.x().domain()[0] && key <= _chart.x().domain()[1])
data.push(d);
});
}
return data;
};
function hasRangeSelected(range) {
return range != null && range != undefined && range instanceof Array && range.length > 1;
}
_chart.focus = function (range) {
_refocused = true;
if (hasRangeSelected(range)) {
_chart.x().domain(range);
} else {
_chart.x().domain(_chart.xOriginalDomain());
}
_chart.redraw();
if (!hasRangeSelected(range))
_refocused = false;
};
_chart.refocused = function () {
return _refocused;
};
return _chart;
};
dc.colorChart = function(_chart) {
var _colors = d3.scale.category20c();
var _colorDomain = [0, _colors.range().length];
var _colorCalculator = function(value) {
var minValue = _colorDomain[0];
var maxValue = _colorDomain[1];
if (isNaN(value)) value = 0;
if(maxValue == null) return _colors(value);
var colorsLength = _chart.colors().range().length;
var denominator = (maxValue - minValue) / colorsLength;
var colorValue = Math.abs(Math.min(colorsLength - 1, Math.round((value - minValue) / denominator)));
return _chart.colors()(colorValue);
};
var _colorAccessor = function(d, i){return i;};
_chart.colors = function(_) {
if (!arguments.length) return _colors;
if (_ instanceof Array) {
_colors = d3.scale.ordinal().range(_);
var domain = [];
for(var i = 0; i < _.length; ++i){
domain.push(i);
}
_colors.domain(domain);
} else {
_colors = _;
}
_colorDomain = [0, _colors.range().length];
return _chart;
};
_chart.colorCalculator = function(_){
if(!arguments.length) return _colorCalculator;
_colorCalculator = _;
return _chart;
};
_chart.getColor = function(d, i){
return _colorCalculator(_colorAccessor(d, i));
};
_chart.colorAccessor = function(_){
if(!arguments.length) return _colorAccessor;
_colorAccessor = _;
return _chart;
};
_chart.colorDomain = function(_){
if(!arguments.length) return _colorDomain;
_colorDomain = _;
return _chart;
};
return _chart;
};
dc.singleSelectionChart = function(_chart) {
var _filter;
var _filterHandler = function(dimension, filter){
dimension.filter(filter);
return filter;
};
_chart.hasFilter = function() {
return _filter != null;
};
_chart.filter = function(_) {
if (!arguments.length) return _filter;
_filter = _;
if (_chart.dataSet() && _chart.dimension().filter != undefined){
var f = _filterHandler(_chart.dimension(), _filter);
_filter = f?f:_filter;
}
if (_) {
_chart.turnOnControls();
} else {
_chart.turnOffControls();
}
_chart.invokeFilteredListener(_chart, _);
return _chart;
};
_chart.highlightSelected = function(e) {
d3.select(e).classed(dc.constants.SELECTED_CLASS, true);
d3.select(e).classed(dc.constants.DESELECTED_CLASS, false);
};
_chart.fadeDeselected = function(e) {
d3.select(e).classed(dc.constants.SELECTED_CLASS, false);
d3.select(e).classed(dc.constants.DESELECTED_CLASS, true);
};
_chart.resetHighlight = function(e) {
d3.select(e).classed(dc.constants.SELECTED_CLASS, false);
d3.select(e).classed(dc.constants.DESELECTED_CLASS, false);
};
_chart.onClick = function(d) {
var toFilter = _chart.keyAccessor()(d);
dc.events.trigger(function() {
_chart.filterTo(toFilter == _chart.filter() ? null : toFilter);
});
};
_chart.filterTo = function(toFilter) {
_chart.filter(toFilter);
dc.redrawAll(_chart.chartGroup());
};
_chart.filterHandler = function(_){
if(!arguments.length) return _filterHandler;
_filterHandler = _;
return _chart;
};
return _chart;
};
dc.stackableChart = function (_chart) {
var MIN_DATA_POINT_HEIGHT = 0;
var _groupStack = new dc.utils.GroupStack();
var _allGroups;
var _allValueAccessors;
var _allKeyAccessors;
_chart.stack = function (group, retriever) {
_groupStack.setDefaultAccessor(_chart.valueAccessor());
_groupStack.addGroup(group, retriever);
_chart.expireCache();
return _chart;
};
_chart.expireCache = function(){
_allGroups = null;
_allValueAccessors = null;
_allKeyAccessors = null;
return _chart;
};
_chart.allGroups = function () {
if (_allGroups == null) {
_allGroups = [];
_allGroups.push(_chart.group());
for (var i = 0; i < _groupStack.size(); ++i)
_allGroups.push(_groupStack.getGroupByIndex(i));
}
return _allGroups;
};
_chart.allValueAccessors = function () {
if (_allValueAccessors == null) {
_allValueAccessors = [];
_allValueAccessors.push(_chart.valueAccessor());
for (var i = 0; i < _groupStack.size(); ++i)
_allValueAccessors.push(_groupStack.getAccessorByIndex(i));
}
return _allValueAccessors;
};
_chart.getValueAccessorByIndex = function (groupIndex) {
return _chart.allValueAccessors()[groupIndex];
};
_chart.yAxisMin = function () {
var min = 0;
var allGroups = _chart.allGroups();
for (var groupIndex = 0; groupIndex < allGroups.length; ++groupIndex) {
var group = allGroups[groupIndex];
var m = dc.utils.groupMin(group, _chart.getValueAccessorByIndex(groupIndex));
if (m < min) min = m;
}
if (min < 0) {
min = 0;
for (var groupIndex = 0; groupIndex < allGroups.length; ++groupIndex) {
var group = allGroups[groupIndex];
min += dc.utils.groupMin(group, _chart.getValueAccessorByIndex(groupIndex));
}
}
min = dc.utils.subtract(min, _chart.yAxisPadding());
return min;
};
_chart.yAxisMax = function () {
var max = 0;
var allGroups = _chart.allGroups();
for (var groupIndex = 0; groupIndex < allGroups.length; ++groupIndex) {
var group = allGroups[groupIndex];
max += dc.utils.groupMax(group, _chart.getValueAccessorByIndex(groupIndex));
}
max = dc.utils.add(max, _chart.yAxisPadding());
return max;
};
_chart.allKeyAccessors = function () {
if (_allKeyAccessors == null) {
_allKeyAccessors = [];
_allKeyAccessors.push(_chart.keyAccessor());
for (var i = 0; i < _groupStack.size(); ++i)
_allKeyAccessors.push(_chart.keyAccessor());
}
return _allKeyAccessors;
};
_chart.getKeyAccessorByIndex = function (groupIndex) {
return _chart.allKeyAccessors()[groupIndex];
};
_chart.xAxisMin = function () {
var min = null;
var allGroups = _chart.allGroups();
for (var groupIndex = 0; groupIndex < allGroups.length; ++groupIndex) {
var group = allGroups[groupIndex];
var m = dc.utils.groupMin(group, _chart.getKeyAccessorByIndex(groupIndex));
if (min == null || min > m) min = m;
}
return dc.utils.subtract(min, _chart.xAxisPadding());
};
_chart.xAxisMax = function () {
var max = null;
var allGroups = _chart.allGroups();
for (var groupIndex = 0; groupIndex < allGroups.length; ++groupIndex) {
var group = allGroups[groupIndex];
var m = dc.utils.groupMax(group, _chart.getKeyAccessorByIndex(groupIndex));
if (max == null || max < m) max = m;
}
return dc.utils.add(max, _chart.xAxisPadding());
};
_chart.baseLineY = function () {
return _chart.y()(0);
}
_chart.dataPointBaseline = function () {
return _chart.margins().top + _chart.baseLineY();
};
function getValueFromData(groupIndex, d) {
return _chart.getValueAccessorByIndex(groupIndex)(d);
}
_chart.dataPointHeight = function (d, groupIndex) {
var value = getValueFromData(groupIndex, d);
var yPosition = _chart.y()(value);
var zeroPosition = _chart.baseLineY();
var h = 0;
if (value > 0)
h = zeroPosition - yPosition;
else
h = yPosition - zeroPosition;
if (isNaN(h) || h < MIN_DATA_POINT_HEIGHT)
h = MIN_DATA_POINT_HEIGHT;
return h;
};
function calculateDataPointMatrix(data, groupIndex) {
for (var dataIndex = 0; dataIndex < data.length; ++dataIndex) {
var d = data[dataIndex];
var value = getValueFromData(groupIndex, d);
if (groupIndex == 0) {
if (value > 0)
_groupStack.setDataPoint(groupIndex, dataIndex, _chart.dataPointBaseline() - _chart.dataPointHeight(d, groupIndex));
else
_groupStack.setDataPoint(groupIndex, dataIndex, _chart.dataPointBaseline());
} else {
if (value > 0)
_groupStack.setDataPoint(groupIndex, dataIndex, _groupStack.getDataPoint(groupIndex - 1, dataIndex) - _chart.dataPointHeight(d, groupIndex))
else if (value < 0)
_groupStack.setDataPoint(groupIndex, dataIndex, _groupStack.getDataPoint(groupIndex - 1, dataIndex) + _chart.dataPointHeight(d, groupIndex - 1))
else // value == 0
_groupStack.setDataPoint(groupIndex, dataIndex, _groupStack.getDataPoint(groupIndex - 1, dataIndex))
}
}
}
_chart.calculateDataPointMatrixForAll = function (groups) {
for (var groupIndex = 0; groupIndex < groups.length; ++groupIndex) {
var group = groups[groupIndex];
var data = group.all();
calculateDataPointMatrix(data, groupIndex);
}
};
_chart.calculateDataPointMatrixWithinXDomain = function (groups) {
for (var groupIndex = 0; groupIndex < groups.length; ++groupIndex) {
var group = groups[groupIndex];
var data = _chart.getDataWithinXDomain(group);
calculateDataPointMatrix(data, groupIndex);
}
};
_chart.getChartStack = function () {
return _groupStack;
};
dc.override(_chart, "valueAccessor", function (_) {
if (!arguments.length) return _chart._valueAccessor();
_chart.expireCache();
return _chart._valueAccessor(_);
});
dc.override(_chart, "keyAccessor", function (_) {
if (!arguments.length) return _chart._keyAccessor();
_chart.expireCache();
return _chart._keyAccessor(_);
});
return _chart;
};
dc.abstractBubbleChart = function (_chart) {
var _maxBubbleRelativeSize = 0.3;
var _minRadiusWithLabel = 10;
_chart.BUBBLE_NODE_CLASS = "node";
_chart.BUBBLE_CLASS = "bubble";
_chart.MIN_RADIUS = 10;
_chart = dc.singleSelectionChart(dc.colorChart(_chart));
_chart.renderLabel(true);
_chart.renderTitle(false);
var _r = d3.scale.linear().domain([0, 100]);
var _rValueAccessor = function (d) {
return d.r;
};
_chart.r = function (_) {
if (!arguments.length) return _r;
_r = _;
return _chart;
};
_chart.radiusValueAccessor = function (_) {
if (!arguments.length) return _rValueAccessor;
_rValueAccessor = _;
return _chart;
};
_chart.rMin = function () {
var min = d3.min(_chart.group().all(), function (e) {
return _chart.radiusValueAccessor()(e);
});
return min;
};
_chart.rMax = function () {
var max = d3.max(_chart.group().all(), function (e) {
return _chart.radiusValueAccessor()(e);
});
return max;
};
_chart.bubbleR = function (d) {
var value = _chart.radiusValueAccessor()(d);
var r = _chart.r()(value);
if (isNaN(r) || value <= 0)
r = 0;
return r;
};
var labelFunction = function (d) {
return _chart.label()(d);
};
var labelOpacity = function (d) {
return (_chart.bubbleR(d) > _minRadiusWithLabel) ? 1 : 0;
};
_chart.doRenderLabel = function (bubbleGEnter) {
if (_chart.renderLabel()) {
var label = bubbleGEnter.select("text");
if (label.empty()) {
label = bubbleGEnter.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.on("click", _chart.onClick);
}
label
.attr("opacity", 0)
.text(labelFunction);
dc.transition(label, _chart.transitionDuration())
.attr("opacity", labelOpacity);
}
};
_chart.doUpdateLabels = function (bubbleGEnter) {
if (_chart.renderLabel()) {
var labels = bubbleGEnter.selectAll("text")
.text(labelFunction);
dc.transition(labels, _chart.transitionDuration())
.attr("opacity", labelOpacity);
}
};
var titleFunction = function (d) {
return _chart.title()(d);
};
_chart.doRenderTitles = function (g) {
if (_chart.renderTitle()) {
var title = g.select("title");
if (title.empty())
g.append("title").text(titleFunction);
}
};
_chart.doUpdateTitles = function (g) {
if (_chart.renderTitle()) {
g.selectAll("title").text(titleFunction);
}
};
_chart.minRadiusWithLabel = function (_) {
if (!arguments.length) return _minRadiusWithLabel;
_minRadiusWithLabel = _;
return _chart;
};
_chart.maxBubbleRelativeSize = function (_) {
if (!arguments.length) return _maxBubbleRelativeSize;
_maxBubbleRelativeSize = _;
return _chart;
};
_chart.initBubbleColor = function (d, i) {
this[dc.constants.NODE_INDEX_NAME] = i;
return _chart.getColor(d, i);
};
_chart.updateBubbleColor = function (d, i) {
// a work around to get correct node index since
// d3 does not send i correctly here
return _chart.getColor(d, this[dc.constants.NODE_INDEX_NAME]);
};
_chart.fadeDeselectedArea = function () {
if (_chart.hasFilter()) {
_chart.selectAll("g." + _chart.BUBBLE_NODE_CLASS).each(function (d) {
if (_chart.isSelectedNode(d)) {
_chart.highlightSelected(this);
} else {
_chart.fadeDeselected(this);
}
});
} else {
_chart.selectAll("g." + _chart.BUBBLE_NODE_CLASS).each(function (d) {
_chart.resetHighlight(this);
});
}
};
_chart.isSelectedNode = function (d) {
return _chart.filter() == d.key;
};
_chart.onClick = function (d) {
var toFilter = d.key;
if (toFilter == _chart.filter()) {
dc.events.trigger(function () {
_chart.filter(null);
dc.redrawAll(_chart.chartGroup());
});
} else {
dc.events.trigger(function () {
_chart.filter(toFilter);
dc.redrawAll(_chart.chartGroup());
});
}
};
return _chart;
};
dc.pieChart = function(parent, chartGroup) {
var DEFAULT_MIN_ANGLE_FOR_LABEL = 0.5;
var _sliceCssClass = "pie-slice";
var _radius = 90, _innerRadius = 0;
var _g;
var _minAngleForLabel = DEFAULT_MIN_ANGLE_FOR_LABEL;
var _chart = dc.singleSelectionChart(dc.colorChart(dc.baseChart({})));
_chart.label(function(d) {
return _chart.keyAccessor()(d.data);
});
_chart.renderLabel(true);
_chart.title(function(d) {
return _chart.keyAccessor()(d.data) + ": " + _chart.valueAccessor()(d.data);
});
_chart.transitionDuration(350);
_chart.doRender = function() {
_chart.resetSvg();
_g = _chart.svg()
.append("g")
.attr("transform", "translate(" + _chart.cx() + "," + _chart.cy() + ")");
drawChart();
return _chart;
};
function drawChart() {
if (_chart.dataSet()) {
var pie = calculateDataPie();
var arc = _chart.buildArcs();
var pieData = pie(_chart.orderedGroup().top(Infinity));
var slices = _g.selectAll("g." + _sliceCssClass)
.data(pieData);
createElements(slices, arc, pieData);
updateElements(pieData, arc);
removeElements(slices);
highlightFilter();
}
}
function createElements(slices, arc, pieData) {
var slicesEnter = createSliceNodes(slices);
createSlicePath(slicesEnter, arc);
createTitles(slicesEnter);
createLabels(pieData, arc);
}
function createSliceNodes(slices) {
var slicesEnter = slices
.enter()
.append("g")
.attr("class", function(d, i) {
return _sliceCssClass + " _" + i;
});
return slicesEnter;
}
function createSlicePath(slicesEnter, arc) {
var slicePath = slicesEnter.append("path")
.attr("fill", function(d, i) {
return _chart.getColor(d, i);
})
.on("click", onClick)
.attr("d", function(d, i) {
return safeArc(d, i, arc);
});
slicePath.transition()
.duration(_chart.transitionDuration())
.attrTween("d", tweenPie);
}
function createTitles(slicesEnter) {
if (_chart.renderTitle()) {
slicesEnter.append("title").text(function(d) {
return _chart.title()(d);
});
}
}
function createLabels(pieData, arc) {
if (_chart.renderLabel()) {
var labels = _g.selectAll("text." + _sliceCssClass)
.data(pieData);
var labelsEnter = labels
.enter()
.append("text")
.attr("class", function(d, i) {
return _sliceCssClass + " _" + i;
})
.on("click", onClick);
dc.transition(labelsEnter, _chart.transitionDuration())
.attr("transform", function(d) {
d.innerRadius = _chart.innerRadius();
d.outerRadius = _radius;
var centroid = arc.centroid(d);
if (isNaN(centroid[0]) || isNaN(centroid[1])) {
return "translate(0,0)";
} else {
return "translate(" + centroid + ")";
}
})
.attr("text-anchor", "middle")
.text(function(d) {
var data = d.data;
if (sliceHasNoData(data) || sliceTooSmall(d))
return "";
return _chart.label()(d);
});
}
}
function updateElements(pieData, arc) {
updateSlicePaths(pieData, arc);
updateLabels(pieData, arc);
updateTitles(pieData);
}
function updateSlicePaths(pieData, arc) {
var slicePaths = _g.selectAll("g." + _sliceCssClass)
.data(pieData)
.select("path")
.attr("d", function(d, i) {
return safeArc(d, i, arc);
});
dc.transition(slicePaths, _chart.transitionDuration(),
function(s) {
s.attrTween("d", tweenPie);
}).attr("fill", function(d, i) {
return _chart.getColor(d, i);
});
}
function updateLabels(pieData, arc) {
if (_chart.renderLabel()) {
var labels = _g.selectAll("text." + _sliceCssClass)
.data(pieData);
dc.transition(labels, _chart.transitionDuration())
.attr("transform", function(d) {
d.innerRadius = _chart.innerRadius();
d.outerRadius = _radius;
var centroid = arc.centroid(d);
if (isNaN(centroid[0]) || isNaN(centroid[1])) {
return "translate(0,0)";
} else {
return "translate(" + centroid + ")";
}
})
.attr("text-anchor", "middle")
.text(function(d) {
var data = d.data;
if (sliceHasNoData(data) || sliceTooSmall(d))
return "";
return _chart.label()(d);
});
}
}
function updateTitles(pieData) {
if (_chart.renderTitle()) {
_g.selectAll("g." + _sliceCssClass)
.data(pieData)
.select("title")
.text(function(d) {
return _chart.title()(d);
});
}
}
function removeElements(slices) {
slices.exit().remove();
}
function highlightFilter() {
if (_chart.hasFilter()) {
_chart.selectAll("g." + _sliceCssClass).each(function(d) {
if (_chart.isSelectedSlice(d)) {
_chart.highlightSelected(this);
} else {
_chart.fadeDeselected(this);
}
});
} else {
_chart.selectAll("g." + _sliceCssClass).each(function(d) {
_chart.resetHighlight(this);
});
}
}
_chart.innerRadius = function(r) {
if (!arguments.length) return _innerRadius;
_innerRadius = r;
return _chart;
};
_chart.radius = function(r) {
if (!arguments.length) return _radius;
_radius = r;
return _chart;
};
_chart.cx = function() {
return _chart.width() / 2;
};
_chart.cy = function() {
return _chart.height() / 2;
};
_chart.buildArcs = function() {
return d3.svg.arc().outerRadius(_radius).innerRadius(_innerRadius);
};
_chart.isSelectedSlice = function(d) {
return _chart.filter() == _chart.keyAccessor()(d.data);
};
_chart.doRedraw = function() {
drawChart();
return _chart;
};
_chart.minAngleForLabel = function(_) {
if (!arguments.length) return _minAngleForLabel;
_minAngleForLabel = _;
return _chart;
};
function calculateDataPie() {
return d3.layout.pie().sort(null).value(function(d) {
return _chart.valueAccessor()(d);
});
}
function sliceTooSmall(d) {
var angle = (d.endAngle - d.startAngle);
return isNaN(angle) || angle < _minAngleForLabel;
}
function sliceHasNoData(data) {
return _chart.valueAccessor()(data) == 0;
}
function tweenPie(b) {
b.innerRadius = _chart.innerRadius();
var current = this._current;
if (isOffCanvas(current))
current = {startAngle: 0, endAngle: 0};
var i = d3.interpolate(current, b);
this._current = i(0);
return function(t) {
return safeArc(i(t), 0, _chart.buildArcs());
};
}
function isOffCanvas(current) {
return current == null || isNaN(current.startAngle) || isNaN(current.endAngle);
}
function onClick(d) {
_chart.onClick(d.data);
}
function safeArc(d, i, arc) {
var path = arc(d, i);
if(path.indexOf("NaN") >= 0)
path = "M0,0";
return path;
}
return _chart.anchor(parent, chartGroup);
};
dc.barChart = function (parent, chartGroup) {
var MIN_BAR_WIDTH = 1;
var DEFAULT_GAP_BETWEEN_BARS = 2;
var _chart = dc.stackableChart(dc.coordinateGridChart(dc.singleSelectionChart({})));
var _gap = DEFAULT_GAP_BETWEEN_BARS;
var _centerBar = false;
var _numberOfBars;
var _barWidth;
_chart.plotData = function () {
var groups = _chart.allGroups();
_chart.calculateDataPointMatrixWithinXDomain(groups);
for (var groupIndex = 0; groupIndex < groups.length; ++groupIndex) {
generateBarsPerGroup(groupIndex, groups[groupIndex]);
}
};
function generateBarsPerGroup(groupIndex, group) {
var bars = _chart.chartBodyG().selectAll("rect." + dc.constants.STACK_CLASS + groupIndex)
.data(_chart.getDataWithinXDomain(group));
addNewBars(bars, groupIndex);
updateBars(bars, groupIndex);
deleteBars(bars);
}
function addNewBars(bars, groupIndex) {
var bars = bars.enter().append("rect");
bars.attr("class", "bar " + dc.constants.STACK_CLASS + groupIndex)
.attr("x", function (data, dataIndex) {
return barX(this, data, groupIndex, dataIndex);
})
.attr("y", _chart.baseLineY())
.attr("width", barWidth);
if (_chart.isOrdinal())
bars.on("click", _chart.onClick);
if (_chart.renderTitle()) {
bars.append("title").text(_chart.title());
}
dc.transition(bars, _chart.transitionDuration())
.attr("y", function (data, dataIndex) {
return barY(this, data, dataIndex);
})
.attr("height", function (data) {
return _chart.dataPointHeight(data, getGroupIndexFromBar(this));
});
}
function updateBars(bars, groupIndex) {
if (_chart.renderTitle()) {
bars.select("title").text(_chart.title());
}
dc.transition(bars, _chart.transitionDuration())
.attr("x", function (data, dataIndex) {
return barX(this, data, groupIndex, dataIndex);
})
.attr("y", function (data, dataIndex) {
return barY(this, data, dataIndex);
})
.attr("height", function (data) {
return _chart.dataPointHeight(data, getGroupIndexFromBar(this));
})
.attr("width", barWidth);
}
function deleteBars(bars) {
dc.transition(bars.exit(), _chart.transitionDuration())
.attr("y", _chart.xAxisY())
.attr("height", 0);
}
function getNumberOfBars() {
if (_numberOfBars == null || _chart.refocused()){
_numberOfBars = _chart.xUnitCount();
}
return _numberOfBars;
}
function barWidth(d) {
if (_barWidth == null || _chart.refocused()) {
var numberOfBars = getNumberOfBars();
var w = MIN_BAR_WIDTH;
if (_chart.isOrdinal())
w = Math.floor(_chart.xAxisLength() / (numberOfBars + 1));
else
w = Math.floor(_chart.xAxisLength() / numberOfBars);
w -= _gap;
if (isNaN(w) || w < MIN_BAR_WIDTH)
w = MIN_BAR_WIDTH;
_barWidth = w;
}
return _barWidth;
}
function setGroupIndexToBar(bar, groupIndex) {
bar[dc.constants.GROUP_INDEX_NAME] = groupIndex;
}
function barX(bar, data, groupIndex, dataIndex) {
setGroupIndexToBar(bar, groupIndex);
var position = _chart.x()(_chart.keyAccessor()(data)) + _chart.margins().left;
if (_centerBar)
position = position - barWidth(data) / 2;
return position;
}
function getGroupIndexFromBar(bar) {
var groupIndex = bar[dc.constants.GROUP_INDEX_NAME];
return groupIndex;
}
function barY(bar, data, dataIndex) {
var groupIndex = getGroupIndexFromBar(bar);
return _chart.getChartStack().getDataPoint(groupIndex, dataIndex);
}
_chart.fadeDeselectedArea = function () {
var bars = _chart.chartBodyG().selectAll("rect.bar");
var extent = _chart.brush().extent();
if (_chart.isOrdinal()) {
if (_chart.filter() != null)
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
var key = _chart.keyAccessor()(d);
return key != _chart.filter();
});
else
bars.classed(dc.constants.DESELECTED_CLASS, false);
} else {
if (!_chart.brushIsEmpty(extent)) {
var start = extent[0];
var end = extent[1];
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
var xValue = _chart.keyAccessor()(d);
return xValue < start || xValue >= end;
});
} else {
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
}
};
_chart.centerBar = function (_) {
if (!arguments.length) return _centerBar;
_centerBar = _;
return _chart;
};
_chart.gap = function (_) {
if (!arguments.length) return _gap;
_gap = _;
return _chart;
};
_chart.extendBrush = function () {
var extent = _chart.brush().extent();
if (_chart.round() && !_centerBar) {
extent[0] = extent.map(_chart.round())[0];
extent[1] = extent.map(_chart.round())[1];
_chart.chartBodyG().select(".brush")
.call(_chart.brush().extent(extent));
}
return extent;
};
dc.override(_chart, "prepareOrdinalXAxis", function () {
return this._prepareOrdinalXAxis(_chart.xUnitCount() + 1);
});
return _chart.anchor(parent, chartGroup);
};
dc.lineChart = function(parent, chartGroup) {
var AREA_BOTTOM_PADDING = 1;
var DEFAULT_DOT_RADIUS = 5;
var TOOLTIP_G_CLASS = "dc-tooltip";
var DOT_CIRCLE_CLASS = "dot";
var Y_AXIS_REF_LINE_CLASS = "yRef";
var X_AXIS_REF_LINE_CLASS = "xRef";
var _chart = dc.stackableChart(dc.coordinateGridChart({}));
var _renderArea = false;
var _dotRadius = DEFAULT_DOT_RADIUS;
_chart.transitionDuration(500);
_chart.plotData = function() {
var groups = _chart.allGroups();
_chart.calculateDataPointMatrixForAll(groups);
for (var groupIndex = 0; groupIndex < groups.length; ++ groupIndex) {
var group = groups[groupIndex];
plotDataByGroup(groupIndex, group);
}
};
function plotDataByGroup(groupIndex, group) {
var stackedCssClass = getStackedCssClass(groupIndex);
var g = createGrouping(stackedCssClass, group);
var line = drawLine(g, stackedCssClass, groupIndex);
if (_renderArea)
drawArea(g, stackedCssClass, groupIndex, line);
if (_chart.renderTitle())
drawDots(g, groupIndex);
}
function getStackedCssClass(groupIndex) {
return dc.constants.STACK_CLASS + groupIndex;
}
function createGrouping(stackedCssClass, group) {
var g = _chart.chartBodyG().select("g." + stackedCssClass);
if (g.empty())
g = _chart.chartBodyG().append("g").attr("class", stackedCssClass);
g.datum(group.all());
return g;
}
function drawLine(g, stackedCssClass, groupIndex) {
var linePath = g.select("path.line");
if (linePath.empty())
linePath = g.append("path")
.attr("class", "line " + stackedCssClass);
linePath[0][0][dc.constants.GROUP_INDEX_NAME] = groupIndex;
var line = d3.svg.line()
.x(lineX)
.y(function(d, dataIndex) {
var groupIndex = this[dc.constants.GROUP_INDEX_NAME];
return lineY(d, dataIndex, groupIndex);
});
dc.transition(linePath, _chart.transitionDuration(),
function(t) {
t.ease("linear");
}).attr("d", line);
return line;
}
var lineX = function(d) {
return _chart.margins().left + _chart.x()(_chart.keyAccessor()(d));
};
var lineY = function(d, dataIndex, groupIndex) {
var y = _chart.getChartStack().getDataPoint(groupIndex, dataIndex);
if(y >= _chart.dataPointBaseline())
y += _chart.dataPointHeight(d, groupIndex);
return y;
};
function drawArea(g, stackedCssClass, groupIndex, line) {
var areaPath = g.select("path.area");
if (areaPath.empty())
areaPath = g.append("path")
.attr("class", "area " + stackedCssClass);
areaPath[0][0][dc.constants.GROUP_INDEX_NAME] = groupIndex;
var area = d3.svg.area()
.x(line.x())
.y1(line.y())
.y0(function(d, dataIndex) {
var groupIndex = this[dc.constants.GROUP_INDEX_NAME];
if (groupIndex == 0)
return _chart.dataPointBaseline() - AREA_BOTTOM_PADDING;
var y = _chart.getChartStack().getDataPoint(groupIndex-1, dataIndex);
if(y < _chart.dataPointBaseline())
return y - AREA_BOTTOM_PADDING;
else
return y + _chart.dataPointHeight(d, groupIndex-1);
});
dc.transition(areaPath, _chart.transitionDuration(),
function(t) {
t.ease("linear");
}).attr("d", area);
}
_chart.renderArea = function(_) {
if (!arguments.length) return _renderArea;
_renderArea = _;
return _chart;
};
function drawDots(parentG, groupIndex) {
var g = parentG.select("g." + TOOLTIP_G_CLASS);
if (g.empty())
g = parentG.append("g").attr("class", TOOLTIP_G_CLASS);
createRefLines(g);
var dots = g.selectAll("circle." + DOT_CIRCLE_CLASS)
.data(g.datum());
dots.enter()
.append("circle")
.attr("class", DOT_CIRCLE_CLASS)
.attr("r", _dotRadius)
.style("fill-opacity", 1e-6)
.style("stroke-opacity", 1e-6)
.on("mousemove", function(d) {
var dot = d3.select(this);
showDot(dot);
showRefLines(dot, g);
})
.on("mouseout", function(d) {
var dot = d3.select(this);
hideDot(dot);
hideRefLines(g);
})
.append("title").text(_chart.title());
dots.attr("cx", lineX)
.attr("cy", function(d, dataIndex) {
return lineY(d, dataIndex, groupIndex);
})
.select("title").text(_chart.title());
dots.exit().remove();
}
function createRefLines(g) {
var yRefLine = g.select("path." + Y_AXIS_REF_LINE_CLASS).empty() ? g.append("path").attr("class", Y_AXIS_REF_LINE_CLASS) : g.select("path." + Y_AXIS_REF_LINE_CLASS);
yRefLine.style("display", "none").attr("stroke-dasharray", "5,5");
var xRefLine = g.select("path." + X_AXIS_REF_LINE_CLASS).empty() ? g.append("path").attr("class", X_AXIS_REF_LINE_CLASS) : g.select("path." + X_AXIS_REF_LINE_CLASS);
xRefLine.style("display", "none").attr("stroke-dasharray", "5,5");
}
function showDot(dot) {
dot.style("fill-opacity", .8);
dot.style("stroke-opacity", .8);
return dot;
}
function showRefLines(dot, g) {
var x = dot.attr("cx");
var y = dot.attr("cy");
g.select("path." + Y_AXIS_REF_LINE_CLASS).style("display", "").attr("d", "M" + _chart.margins().left + " " + y + "L" + (x) + " " + (y));
g.select("path." + X_AXIS_REF_LINE_CLASS).style("display", "").attr("d", "M" + x + " " + (_chart.height() - _chart.margins().bottom) + "L" + x + " " + y);
}
function hideDot(dot) {
dot.style("fill-opacity", 1e-6).style("stroke-opacity", 1e-6);
}
function hideRefLines(g) {
g.select("path." + Y_AXIS_REF_LINE_CLASS).style("display", "none");
g.select("path." + X_AXIS_REF_LINE_CLASS).style("display", "none");
}
_chart.dotRadius = function(_) {
if (!arguments.length) return _dotRadius;
_dotRadius = _;
return _chart;
};
return _chart.anchor(parent, chartGroup);
};
dc.dataCount = function(parent, chartGroup) {
var _formatNumber = d3.format(",d");
var _chart = dc.baseChart({});
_chart.doRender = function() {
_chart.selectAll(".total-count").text(_formatNumber(_chart.dimension().size()));
_chart.selectAll(".filter-count").text(_formatNumber(_chart.group().value()));
return _chart;
};
_chart.doRedraw = function(){
return _chart.doRender();
};
return _chart.anchor(parent, chartGroup);
};
dc.dataTable = function(parent, chartGroup) {
var LABEL_CSS_CLASS = "dc-table-label";
var ROW_CSS_CLASS = "dc-table-row";
var COLUMN_CSS_CLASS = "dc-table-column";
var GROUP_CSS_CLASS = "dc-table-group";
var _chart = dc.baseChart({});
var _size = 25;
var _columns = [];
var _sortBy = function(d) {
return d;
};
var _order = d3.ascending;
var _sort;
_chart.doRender = function() {
_chart.selectAll("tbody").remove();
renderRows(renderGroups());
return _chart;
};
function renderGroups() {
var groups = _chart.root().selectAll("tbody")
.data(nestEntries(), function(d) {
return _chart.keyAccessor()(d);
});
var rowGroup = groups
.enter()
.append("tbody");
rowGroup
.append("tr")
.attr("class", GROUP_CSS_CLASS)
.append("td")
.attr("class", LABEL_CSS_CLASS)
.attr("colspan", _columns.length)
.html(function(d) {
return _chart.keyAccessor()(d);
});
groups.exit().remove();
return rowGroup;
}
function nestEntries() {
if (!_sort)
_sort = crossfilter.quicksort.by(_sortBy);
var entries = _chart.dimension().top(_size);
return d3.nest()
.key(_chart.group())
.sortKeys(_order)
.entries(_sort(entries, 0, entries.length));
}
function renderRows(groups) {
var rows = groups.order()
.selectAll("tr." + ROW_CSS_CLASS)
.data(function(d) {
return d.values;
});
var rowEnter = rows.enter()
.append("tr")
.attr("class", ROW_CSS_CLASS);
for (var i = 0; i < _columns.length; ++i) {
var f = _columns[i];
rowEnter.append("td")
.attr("class", COLUMN_CSS_CLASS + " _" + i)
.html(function(d) {
return f(d);
});
}
rows.exit().remove();
return rows;
}
_chart.doRedraw = function() {
return _chart.doRender();
};
_chart.size = function(s) {
if (!arguments.length) return _size;
_size = s;
return _chart;
};
_chart.columns = function(_) {
if (!arguments.length) return _columns;
_columns = _;
return _chart;
};
_chart.sortBy = function(_) {
if (!arguments.length) return _sortBy;
_sortBy = _;
return _chart;
};
_chart.order = function(_) {
if (!arguments.length) return _order;
_order = _;
return _chart;
};
return _chart.anchor(parent, chartGroup);
};
dc.bubbleChart = function(parent, chartGroup) {
var _chart = dc.abstractBubbleChart(dc.coordinateGridChart({}));
var _elasticRadius = false;
_chart.transitionDuration(750);
var bubbleLocator = function(d) {
return "translate(" + (bubbleX(d)) + "," + (bubbleY(d)) + ")";
};
_chart.elasticRadius = function(_) {
if (!arguments.length) return _elasticRadius;
_elasticRadius = _;
return _chart;
};
_chart.plotData = function() {
if (_elasticRadius)
_chart.r().domain([_chart.rMin(), _chart.rMax()]);
_chart.r().range([_chart.MIN_RADIUS, _chart.xAxisLength() * _chart.maxBubbleRelativeSize()]);
var bubbleG = _chart.chartBodyG().selectAll("g." + _chart.BUBBLE_NODE_CLASS)
.data(_chart.group().all());
renderNodes(bubbleG);
updateNodes(bubbleG);
removeNodes(bubbleG);
_chart.fadeDeselectedArea();
};
function renderNodes(bubbleG) {
var bubbleGEnter = bubbleG.enter().append("g");
bubbleGEnter
.attr("class", _chart.BUBBLE_NODE_CLASS)
.attr("transform", bubbleLocator)
.append("circle").attr("class", function(d, i) {
return _chart.BUBBLE_CLASS + " _" + i;
})
.on("click", _chart.onClick)
.attr("fill", _chart.initBubbleColor)
.attr("r", 0);
dc.transition(bubbleG, _chart.transitionDuration())
.attr("r", function(d) {
return _chart.bubbleR(d);
})
.attr("opacity", function(d) {
return (_chart.bubbleR(d) > 0) ? 1 : 0;
});
_chart.doRenderLabel(bubbleGEnter);
_chart.doRenderTitles(bubbleGEnter);
}
function updateNodes(bubbleG) {
dc.transition(bubbleG, _chart.transitionDuration())
.attr("transform", bubbleLocator)
.selectAll("circle." + _chart.BUBBLE_CLASS)
.attr("fill", _chart.updateBubbleColor)
.attr("r", function(d) {
return _chart.bubbleR(d);
})
.attr("opacity", function(d) {
return (_chart.bubbleR(d) > 0) ? 1 : 0;
});
_chart.doUpdateLabels(bubbleG);
_chart.doUpdateTitles(bubbleG);
}
function removeNodes(bubbleG) {
bubbleG.exit().remove();
}
function bubbleX(d) {
var x = _chart.x()(_chart.keyAccessor()(d)) + _chart.margins().left;
if (isNaN(x))
x = 0;
return x;
}
function bubbleY(d) {
var y = _chart.margins().top + _chart.y()(_chart.valueAccessor()(d));
if (isNaN(y))
y = 0;
return y;
}
_chart.renderBrush = function(g) {
// override default x axis brush from parent chart
};
_chart.redrawBrush = function(g) {
// override default x axis brush from parent chart
_chart.fadeDeselectedArea();
};
return _chart.anchor(parent, chartGroup);
};
dc.compositeChart = function(parent, chartGroup) {
var SUB_CHART_CLASS = "sub";
var _chart = dc.coordinateGridChart({});
var _children = [];
_chart.transitionDuration(500);
dc.override(_chart, "generateG", function() {
var g = this._generateG();
for (var i = 0; i < _children.length; ++i) {
var child = _children[i];
generateChildG(child, i);
if (child.dimension() == null) child.dimension(_chart.dimension());
if (child.group() == null) child.group(_chart.group());
child.chartGroup(_chart.chartGroup());
child.svg(_chart.svg());
child.height(_chart.height());
child.width(_chart.width());
child.margins(_chart.margins());
child.xUnits(_chart.xUnits());
child.transitionDuration(_chart.transitionDuration());
}
return g;
});
function generateChildG(child, i) {
child.generateG(_chart.g());
child.g().attr("class", SUB_CHART_CLASS + " _" + i);
}
_chart.plotData = function() {
for (var i = 0; i < _children.length; ++i) {
var child = _children[i];
if (child.g() == null) {
generateChildG(child, i);
}
child.x(_chart.x());
child.y(_chart.y());
child.xAxis(_chart.xAxis());
child.yAxis(_chart.yAxis());
child.plotData();
child.invokeRenderlet(child);
}
};
_chart.fadeDeselectedArea = function() {
for (var i = 0; i < _children.length; ++i) {
var child = _children[i];
child.brush(_chart.brush());
child.fadeDeselectedArea();
}
};
_chart.compose = function(charts) {
_children = charts;
return _chart;
};
_chart.children = function(){
return _children;
};
function getAllYAxisMinFromChildCharts() {
var allMins = [];
for (var i = 0; i < _children.length; ++i) {
allMins.push(_children[i].yAxisMin());
}
return allMins;
}
_chart.yAxisMin = function() {
return d3.min(getAllYAxisMinFromChildCharts());
};
function getAllYAxisMaxFromChildCharts() {
var allMaxes = [];
for (var i = 0; i < _children.length; ++i) {
allMaxes.push(_children[i].yAxisMax());
}
return allMaxes;
}
_chart.yAxisMax = function() {
return dc.utils.add(d3.max(getAllYAxisMaxFromChildCharts()), _chart.yAxisPadding());
};
function getAllXAxisMinFromChildCharts() {
var allMins = [];
for (var i = 0; i < _children.length; ++i) {
allMins.push(_children[i].xAxisMin());
}
return allMins;
}
_chart.xAxisMin = function() {
return dc.utils.subtract(d3.min(getAllXAxisMinFromChildCharts()), _chart.xAxisPadding());
};
function getAllXAxisMaxFromChildCharts() {
var allMaxes = [];
for (var i = 0; i < _children.length; ++i) {
allMaxes.push(_children[i].xAxisMax());
}
return allMaxes;
}
_chart.xAxisMax = function() {
return dc.utils.add(d3.max(getAllXAxisMaxFromChildCharts()), _chart.xAxisPadding());
};
return _chart.anchor(parent, chartGroup);
};
dc.geoChoroplethChart = function (parent, chartGroup) {
var _chart = dc.singleSelectionChart(dc.colorChart(dc.baseChart({})));
_chart.colorAccessor(function (d, i) {
return d;
});
var _geoPath = d3.geo.path();
var _geoJsons = [];
_chart.doRender = function () {
_chart.resetSvg();
for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) {
var states = _chart.svg().append("g")
.attr("class", "layer" + layerIndex);
var regionG = states.selectAll("g." + geoJson(layerIndex).name)
.data(geoJson(layerIndex).data)
.enter()
.append("g")
.attr("class", geoJson(layerIndex).name);
regionG
.append("path")
.attr("fill", "white")
.attr("d", _geoPath);
regionG.append("title");
plotData(layerIndex);
}
};
function plotData(layerIndex) {
var maxValue = dc.utils.groupMax(_chart.group(), _chart.valueAccessor());
var data = generateLayeredData();
if (isDataLayer(layerIndex)) {
var regionG = renderRegionG(layerIndex);
renderPaths(regionG, layerIndex, data, maxValue);
renderTitle(regionG, layerIndex, data);
}
}
function generateLayeredData() {
var data = {};
var groupAll = _chart.group().all();
for (var i = 0; i < groupAll.length; ++i) {
data[_chart.keyAccessor()(groupAll[i])] = _chart.valueAccessor()(groupAll[i]);
}
return data;
}
function isDataLayer(layerIndex) {
return geoJson(layerIndex).keyAccessor;
}
function renderRegionG(layerIndex) {
var regionG = _chart.svg()
.selectAll(layerSelector(layerIndex))
.classed("selected", function (d) {
return isSelected(layerIndex, d);
})
.classed("deselected", function (d) {
return isDeselected(layerIndex, d);
})
.attr("class", function (d) {
var layerNameClass = geoJson(layerIndex).name;
var regionClass = dc.utils.nameToId(geoJson(layerIndex).keyAccessor(d));
var baseClasses = layerNameClass + " " + regionClass;
if (isSelected(layerIndex, d)) baseClasses += " selected";
if (isDeselected(layerIndex, d)) baseClasses += " deselected";
return baseClasses;
});
return regionG;
}
function layerSelector(layerIndex) {
return "g.layer" + layerIndex + " g." + geoJson(layerIndex).name;
}
function isSelected(layerIndex, d) {
return _chart.hasFilter() && _chart.filter() == getKey(layerIndex, d);
}
function isDeselected(layerIndex, d) {
return _chart.hasFilter() && _chart.filter() != getKey(layerIndex, d);
}
function getKey(layerIndex, d) {
return geoJson(layerIndex).keyAccessor(d);
}
function geoJson(index) {
return _geoJsons[index];
}
function renderPaths(regionG, layerIndex, data, maxValue) {
var paths = regionG
.select("path")
.attr("fill", function (d) {
var currentFill = d3.select(this).attr("fill");
if (currentFill)
return currentFill;
return "none";
})
.on("click", function (d) {
return _chart.onClick(d, layerIndex);
});
dc.transition(paths, _chart.transitionDuration()).attr("fill", function (d, i) {
return _chart.getColor(data[geoJson(layerIndex).keyAccessor(d)], i);
});
}
_chart.onClick = function (d, layerIndex) {
var selectedRegion = geoJson(layerIndex).keyAccessor(d);
if (selectedRegion == _chart.filter()) {
dc.events.trigger(function () {
_chart.filter(null);
dc.redrawAll(_chart.chartGroup());
});
} else {
dc.events.trigger(function () {
_chart.filter(selectedRegion);
dc.redrawAll(_chart.chartGroup());
});
}
};
function renderTitle(regionG, layerIndex, data) {
if (_chart.renderTitle()) {
regionG.selectAll("title").text(function (d) {
var key = getKey(layerIndex, d);
var value = data[key];
return _chart.title()({key: key, value: value});
});
}
}
_chart.doRedraw = function () {
for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) {
plotData(layerIndex);
}
};
_chart.overlayGeoJson = function (json, name, keyAccessor) {
for (var i = 0; i < _geoJsons.length; ++i) {
if (_geoJsons[i].name == name) {
_geoJsons[i].data = json;
_geoJsons[i].keyAccessor = keyAccessor;
return _chart
}
}
_geoJsons.push({name: name, data: json, keyAccessor: keyAccessor});
return _chart;
};
_chart.projection = function (projection) {
_geoPath.projection(projection);
return _chart;
};
_chart.geoJsons = function () {
return _geoJsons;
};
_chart.removeGeoJson = function (name) {
var geoJsons = [];
for (var i = 0; i < _geoJsons.length; ++i) {
var layer = _geoJsons[i];
if (layer.name != name) {
geoJsons.push(layer);
}
}
_geoJsons = geoJsons;
return _chart;
};
return _chart.anchor(parent, chartGroup);
};
dc.bubbleOverlay = function(root, chartGroup) {
var BUBBLE_OVERLAY_CLASS = "bubble-overlay";
var BUBBLE_NODE_CLASS = "node";
var BUBBLE_CLASS = "bubble";
var _chart = dc.abstractBubbleChart(dc.baseChart({}));
var _g;
var _points = [];
_chart.transitionDuration(750);
_chart.radiusValueAccessor(function(d) {
return d.value;
});
_chart.point = function(name, x, y) {
_points.push({name: name, x: x, y: y});
return _chart;
};
_chart.doRender = function() {
_g = initOverlayG();
_chart.r().range([_chart.MIN_RADIUS, _chart.width() * _chart.maxBubbleRelativeSize()]);
initializeBubbles();
_chart.fadeDeselectedArea();
return _chart;
};
function initOverlayG() {
_g = _chart.select("g." + BUBBLE_OVERLAY_CLASS);
if (_g.empty())
_g = _chart.svg().append("g").attr("class", BUBBLE_OVERLAY_CLASS);
return _g;
}
function initializeBubbles() {
var data = mapData();
_points.forEach(function(point) {
var nodeG = getNodeG(point, data);
var circle = nodeG.select("circle." + BUBBLE_CLASS);
if (circle.empty())
circle = nodeG.append("circle")
.attr("class", BUBBLE_CLASS)
.attr("r", 0)
.attr("fill", _chart.initBubbleColor)
.on("click", _chart.onClick);
dc.transition(circle, _chart.transitionDuration())
.attr("r", function(d) {
return _chart.bubbleR(d);
});
_chart.doRenderLabel(nodeG);
_chart.doRenderTitles(nodeG);
});
}
function mapData() {
var data = {};
_chart.group().all().forEach(function(datum) {
data[_chart.keyAccessor()(datum)] = datum;
});
return data;
}
function getNodeG(point, data) {
var bubbleNodeClass = BUBBLE_NODE_CLASS + " " + dc.utils.nameToId(point.name);
var nodeG = _g.select("g." + dc.utils.nameToId(point.name));
if (nodeG.empty()) {
nodeG = _g.append("g")
.attr("class", bubbleNodeClass)
.attr("transform", "translate(" + point.x + "," + point.y + ")");
}
nodeG.datum(data[point.name]);
return nodeG;
}
_chart.doRedraw = function() {
updateBubbles();
_chart.fadeDeselectedArea();
return _chart;
};
function updateBubbles() {
var data = mapData();
_points.forEach(function(point) {
var nodeG = getNodeG(point, data);
var circle = nodeG.select("circle." + BUBBLE_CLASS);
dc.transition(circle, _chart.transitionDuration())
.attr("r", function(d) {
return _chart.bubbleR(d);
})
.attr("fill", _chart.updateBubbleColor);
_chart.doUpdateLabels(nodeG);
_chart.doUpdateTitles(nodeG);
});
}
_chart.debug = function(flag) {
if(flag){
var debugG = _chart.select("g." + dc.constants.DEBUG_GROUP_CLASS);
if(debugG.empty())
debugG = _chart.svg()
.append("g")
.attr("class", dc.constants.DEBUG_GROUP_CLASS);
var debugText = debugG.append("text")
.attr("x", 10)
.attr("y", 20);
debugG
.append("rect")
.attr("width", _chart.width())
.attr("height", _chart.height())
.on("mousemove", function() {
var position = d3.mouse(debugG.node());
var msg = position[0] + ", " + position[1];
debugText.text(msg);
});
}else{
_chart.selectAll(".debug").remove();
}
return _chart;
};
_chart.anchor(root, chartGroup);
return _chart;
};dc.rowChart = function(parent, chartGroup) {
var _g;
var _labelOffsetX = 10;
var _labelOffsetY = 15;
var _gap = 5;
var _rowCssClass = "row";
var _chart = dc.marginable(dc.singleSelectionChart(dc.colorChart(dc.baseChart({}))));
var _xScale;
var _xAxis = d3.svg.axis().orient("bottom");
_chart.doRender = function() {
_xScale = d3.scale.linear().domain([0, d3.max(_chart.group().all(), _chart.valueAccessor())]).range([0, _chart.effectiveWidth()]);
_chart.resetSvg();
_g = _chart.svg()
.append("g")
.attr("transform", "translate(" + _chart.margins().left + "," + _chart.margins().top + ")");
_xAxis.scale(_xScale);
_g.append("g").attr("class", "axis")
.attr("transform", "translate(0, " + _chart.effectiveHeight() + ")")
.call(_xAxis);
drawGridLines();
drawChart();
return _chart;
};
_chart.title(function (d) {
return _chart.keyAccessor()(d) + ": " + _chart.valueAccessor()(d) ;
});
_chart.label(function (d) {
return _chart.keyAccessor()(d);
});
function drawGridLines() {
var ticks = _xAxis.tickValues() ? _xAxis.tickValues() : _xScale.ticks(_xAxis.ticks()[0]);
var gridLineG = _g.append("g")
.attr("class", "grid-line vertical");
var lines = gridLineG.selectAll("line")
.data(ticks);
var linesGEnter = lines.enter()
.append("line")
.attr("x1", function (d) { return _xScale(d); })
.attr("y1", function (d) { return 0; })
.attr("x2", function (d) { return _xScale(d); })
.attr("y2", function (d) { return _chart.effectiveHeight(); });
}
function drawChart() {
var rows = _g.selectAll("g." + _rowCssClass)
.data(_chart.group().all());
createElements(rows, _chart.group().all());
removeElements(rows);
updateElements(rows);
}
function createElements(rows, rowData) {
var rowEnter = rows.enter()
.append("g")
.attr("class", function(d, i) {
return _rowCssClass + " _" + i;
});
rowEnter.append("rect").attr("width", 0);
createTitles(rowEnter);
createLabels(rowEnter);
updateLabels(rows);
}
function removeElements(rows) {
rows.exit().remove();
}
function updateElements(rows) {
var n = _chart.group().all().length;
var height = (_chart.effectiveHeight() - (n + 1) * _gap) / n;
var rect = rows.attr("transform", function(d, i) { return "translate(0," + ((i + 1) * _gap + i * height) + ")"; })
.select("rect")
.attr("height", height)
.attr("fill", _chart.getColor)
.on("click", onClick)
.classed("deselected", function (d) { return (_chart.hasFilter()) ? !_chart.isSelectedRow(d) : false; })
.classed("selected", function (d) { return (_chart.hasFilter()) ? _chart.isSelectedRow(d) : false; });
dc.transition(rect, _chart.transitionDuration())
.attr("width", function(d) {
return _xScale(_chart.valueAccessor()(d));
});
}
function createTitles(rowEnter) {
if (_chart.renderTitle()) {
rowEnter.append("title").text(function(d) {
return _chart.title()(d);
});
}
}
function createLabels(rowEnter) {
if (_chart.renderLabel()) {
rowEnter.append("text");
}
}
function updateLabels(rows) {
if (_chart.renderLabel()) {
rows.select("text")
.attr("x", _labelOffsetX)
.attr("y", _labelOffsetY)
.attr("class", function (d, i) {
return _rowCssClass + " _" + i;
})
.text(function(d) {
return _chart.label()(d);
});
}
}
function numberOfRows() {
return _chart.group().all().length;
}
function rowHeight() {
var n = numberOfRows();
return (_chart.effectiveHeight() - (n + 1) * _gap) / n;
}
function onClick(d) {
_chart.onClick(d);
}
_chart.doRedraw = function() {
drawChart();
return _chart;
};
_chart.xAxis = function () {
return _xAxis;
};
_chart.gap = function(g) {
if (!arguments.length) return _gap;
_gap = g;
return _chart;
};
_chart.labelOffsetX = function (o) {
if (!arguments.length) return _labelOffsetX;
_labelOffset = o;
return _chart;
};
_chart.labelOffsetY = function (o) {
if (!aruguments.length) return _labelOffsetY;
_labelOffset = o;
return _chart;
};
_chart.isSelectedRow = function (d) {
return _chart.filter() == _chart.keyAccessor()(d);
};
return _chart.anchor(parent, chartGroup);
};
function pr(msg) {
console.log(msg)
}
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var latest,oldest;
var TheBuffer = false
function Push2Buffer( NewVal ) {
console.log( " = = = = = = = = " )
console.log( "Push2Buffer()" )
console.log( "\t"+NewVal )
if ( TheBuffer == false) {
if( ! NewVal ) {
// var limits = [ new Date( oldest[0],oldest[1],oldest[2] ) , new Date( latest[0],latest[1],latest[2] ) ];
var limits = [new Date(oldest[0],oldest[1]-1,oldest[2]), new Date(latest[0],latest[1]-1,latest[2] ) ];
limits[0] = new Date(limits[0].setDate(limits[0].getDate()-1) );
limits[1] = new Date(limits[1].setDate(limits[1].getDate()+1) );
NewVal = limits;
}
console.log( " - - - - - - " )
console.log( "\tchanging to:" )
console.log( NewVal )
TheBuffer = NewVal;
Final_UpdateTable( "changerange" )
console.log( "- - - - - - -\n" )
return 1;
}
if ( TheBuffer != false ) {
var past = TheBuffer[0]+"_"+TheBuffer[1]
if( ! NewVal ) {
var limits = [new Date(oldest[0],oldest[1]-1,oldest[2]), new Date(latest[0],latest[1]-1,latest[2] ) ];
limits[0] = new Date(limits[0].setDate(limits[0].getDate()-1) );
limits[1] = new Date(limits[1].setDate(limits[1].getDate()+1) );
NewVal = limits;
}
var now = NewVal[0]+"_"+NewVal[1]
if ( past != now ) {
console.log( " - - - - - - " )
console.log( "\tchanging to:" )
console.log( NewVal )
TheBuffer = NewVal;
Final_UpdateTable( "changerange" )
console.log( "- - - - - - -\n" )
}
return 1;
}
}
function Final_UpdateTable( action ) {
// (1) Identifying if the button is collapsed:
var isCollapsed=false;
var accordiontext = $("#collapseOne").attr("class")
if(accordiontext.indexOf("collapse in")>-1)
isCollapsed=true;
var UpdateTable = false
if ( (action == "click" && !isCollapsed) || (action=="changerange" && isCollapsed) ) {
UpdateTable = true;
$("#corpusdisplayer").html("Close Folder")
} else $("#corpusdisplayer").html("Open Folder")
pr("update table??: "+UpdateTable)
if ( ! UpdateTable ) return false; //stop whatever you wanted to do.
var TimeRange = AjaxRecords;
var dataini = TheBuffer[0].toISOString().split("T")[0]
var datafin = TheBuffer[1].toISOString().split("T")[0]
pr("show me the pubs of the selected period")
console.log( TheBuffer )
pr("\tfrom ["+dataini+"] to ["+datafin+"]")
TimeRange = []
for (var i in AjaxRecords) {
if(AjaxRecords[i].date>=dataini && AjaxRecords[i].date<=datafin){
// pr( AjaxRecords[i].date+" : "+AjaxRecords[i].id )
TimeRange.push(AjaxRecords[i])
}
}
MyTable = $('#my-ajax-table').dynatable({
dataset: {
records: TimeRange
},
features: {
pushState: false,
sort: false
},
writers: {
_rowWriter: ulWriter
// _cellWriter: customCellWriter
}
});
MyTable.data('dynatable').settings.dataset.originalRecords = []
MyTable.data('dynatable').settings.dataset.originalRecords = TimeRange;
MyTable.data('dynatable').paginationPage.set(1);
MyTable.data('dynatable').process();
}
// STEP 01:
// Get all the duplicates using the Django-Garg API
var current_docs = {}
var BIS_dict = {}
var url_elems = window.location.href.split("/")
var url_mainIDs = {}
for(var i=0; i<url_elems.length; i++) {
// if the this element is a number:
if(url_elems[i]!="" && !isNaN(Number(url_elems[i]))) {
url_mainIDs[url_elems[i-1]] = Number(url_elems[i]);
}
}
// STEP 02:
// D3.js: Interactive timerange.
var moveChart = dc.compositeChart("#monthly-move-chart");
var volumeChart = dc.barChart("#monthly-volume-chart");
var dateFormat = d3.time.format("%Y-%m-%d");
var numberFormat = d3.format(".2f");
var MyTable;
var RecDict={};
var AjaxRecords = []
var Garbage = {}
function getRecord(rec_id) {
return MyTable.data('dynatable').settings.dataset.originalRecords[rec_id];
// return AjaxRecords[rec_id]
}
function getRecords() {
return MyTable.data('dynatable').settings.dataset.originalRecords;
}
function transformContent2(rec_id) {
// pr("\t\ttransformContent2: "+rec_id)
var elem = AjaxRecords[rec_id];
// pr("\t"+elem.title)
var result = {}
if (elem["del"]) {
result["id"] = elem["id"]
result["date"] = '<strike>'+elem["date"]+'</strike>'
result["name"] = '<strike>'+elem["name"]+'</strike>'
result["del"] = '<input id='+rec_id+' onclick="overRide(this)" type="checkbox" checked/>'
} else {
result["id"] = elem["id"]
result["date"] = elem["date"]
result["name"] = elem["name"]
result["del"] = '<input id='+rec_id+' onclick="overRide(this)" type="checkbox"/>'
}
return result;
}
function overRide(elem) {
var id = elem.id
var val = elem.checked
console.log("striking: ")
console.log(AjaxRecords[id])
// MyTable.data('dynatable').settings.dataset.originalRecords[id]["del"] = val;
AjaxRecords[id]["del"] = val;
if(val) Garbage[id] = true;
else delete Garbage[id];
if(Object.keys(Garbage).length>0) $("#move2trash").show();
else $("#move2trash").hide();
// console.log(MyTable.data('dynatable').settings.dataset.originalRecords[id])
MyTable.data('dynatable').dom.update();
}
function transformContent(rec_id , header , content) {
if(header=="del") {
// pr("\t\ttransformContent1: "+rec_id)
if(content==true) return '<input id='+rec_id+' onclick="overRide(this)" type="checkbox" checked/>'
if(content==false) return '<input id='+rec_id+' onclick="overRide(this)" type="checkbox"/>'
} else return content;
}
$("#move2trash")
.click(function(){
var ids2trash = []
for(var i in Garbage) {
ids2trash.push(AjaxRecords[i].id);
}
console.log("ids to the trash:")
console.log(ids2trash)
$.ajax({
url: "/tests/move2trash/",
data: "nodeids="+JSON.stringify(ids2trash),
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
},
success: function(data) {
console.log("in #move2trash")
console.log(data)
location.reload();
},
error: function(result) {
console.log("Data not found in #move2trash");
console.log(result)
}
});
})
.hide();
//generic enough
function ulWriter(rowIndex, record, columns, cellWriter) {
// pr("\tulWriter: "+record.id)
var tr = '';
var cp_rec = {}
if(!MyTable) {
for(var i in record) {
cp_rec[i] = transformContent(RecDict[record.id], i , record[i])
}
} else {
// pr("\t\tbfr transf2: rec_id="+record.id+" | arg="+RecDict[record.id])
cp_rec = transformContent2(RecDict[record.id])
}
// grab the record's attribute for each column
for (var i = 0, len = columns.length; i < len; i++) {
tr += cellWriter(columns[i], cp_rec);
}
return '<tr>' + tr + '</tr>';
}
function Main_test( Data , SearchFilter ) {
var DistributionDict = {}
for(var i in DistributionDict)
delete DistributionDict[i];
delete DistributionDict;
DistributionDict = {}
$("#div-table").html("")
$("#div-table").empty();
var div_table = '<p align="right">'+"\n"
div_table += '<table id="my-ajax-table" class="table table-bordered">'+"\n"
div_table += "\t"+'<thead>'+"\n"
div_table += "\t"+"\t"+'<th width="100px;" data-dynatable-column="date">Date</th>'+"\n"
div_table += "\t"+"\t"+'<th data-dynatable-column="name">Title</th>'+"\n"
div_table += "\t"+"\t"+'<th data-dynatable-column="del" data-dynatable-no-sort="true">Trash</th>'+"\n"
div_table += "\t"+"\t"+'</th>'+"\n"
div_table += "\t"+'</thead>'+"\n"
div_table += "\t"+'<tbody>'+"\n"
div_table += "\t"+'</tbody>'+"\n"
div_table += '</table>'+"\n"
div_table += '</p>';
$("#div-table").html(div_table)
var justdates = {}
for(var i in Data) {
var date = Data[i]["date"];
if ( ! justdates[date] ) justdates[date] = 0;
justdates[date]++;
// console.log(Data[i]["date"]+" : originalRecords["+arr_id+"] <- "+orig_id+" | "+Data[i]["name"])
}
var t0 = AjaxRecords[0].date.split("-").map(Number)
var t1 = AjaxRecords.slice(-1)[0].date.split("-").map(Number)
oldest = t0;
latest = t1;
TheBuffer = [new Date(t0[0],t0[1]-1,t0[2]), new Date(t1[0],t1[1]-1,t1[2] ) ];
TheBuffer[0] = new Date(TheBuffer[0].setDate(TheBuffer[0].getDate()-1) );
TheBuffer[1] = new Date(TheBuffer[1].setDate(TheBuffer[1].getDate()+1) );
var arrayd3 = []
for(var e in Data) {
var date = Data[e]["date"];
if(justdates[date]!=false) {
var info = {}
info.date = date
info.dd = dateFormat.parse(date)
info.month = d3.time.month(info.dd)
info.volume = justdates[date]
arrayd3.push(info)
justdates[date] = false;
}
}
for(var i in justdates)
delete justdates[i];
delete justdates;
var ndx = crossfilter(arrayd3);
var all = ndx.groupAll();
//volumeChart:(1)
//moveChart:(1)
// monthly index avg fluctuation in percentage
var moveMonths = ndx.dimension(function (d) {
return d.month;
});
//moveChart:(3)
var monthlyMoveGroup = moveMonths.group().reduceSum(function (d) {
return d.volume;
//return Math.abs(+d.close - +d.open);
});
//volumeChart:(2)
var volumeByMonthGroup = moveMonths.group().reduceSum(function (d) {
return d.volume / 500000;
});
//moveChart:(2)
var indexAvgByMonthGroup = moveMonths.group().reduce(
function (p, v) {
++p.days;
p.total += (+v.open + +v.close) / 2;
p.avg = Math.round(p.total / p.days);
return p;
},
function (p, v) {
--p.days;
p.total -= (+v.open + +v.close) / 2;
p.avg = p.days == 0 ? 0 : Math.round(p.total / p.days);
return p;
},
function () {
return {days: 0, total: 0, avg: 0};
}
);
moveChart.width(800)
.height(150)
.transitionDuration(1000)
.margins({top: 10, right: 50, bottom: 25, left: 40})
.dimension(moveMonths)
.group(indexAvgByMonthGroup)
.valueAccessor(function (d) {
return d.value.avg;
})
.x(d3.time.scale().domain([new Date(t0[0],t0[1],t0[2]), new Date(t1[0],t1[1],t1[2])]))
.round(d3.time.month.round)
.xUnits(d3.time.months)
.elasticY(true)
.renderHorizontalGridLines(true)
.brushOn(false)
.compose([
dc.lineChart(moveChart)
.group(indexAvgByMonthGroup)
.valueAccessor(function (d) {
return d.value.avg;
})
.renderArea(true)
.stack(monthlyMoveGroup, function (d) {
return d.value;
})
.title(function (d) {
var value = d.value.avg ? d.value.avg : d.value;
if (isNaN(value)) value = 0;
return dateFormat(d.key) + "\n" + numberFormat(value);
})
])
.xAxis();
volumeChart.width(800)
.height(100)
.margins({top: 0, right: 50, bottom: 20, left: 40})
.dimension(moveMonths)
.group(volumeByMonthGroup)
.centerBar(true)
.gap(0)
.x(d3.time.scale().domain([TheBuffer[0], TheBuffer[1] ]))
.round(d3.time.month.round)
.xUnits(d3.time.months)
.renderlet(function (chart) {
chart.select("g.y").style("display", "none");
moveChart.filter(chart.filter());
})
.on("filtered", function (chart) {
dc.events.trigger(function () {
var chartfilt = chart.filter()
// tricky part: identifying when the moveChart changes.
if(chartfilt) {
Push2Buffer ( chart.filter() )
} else {
if(TheBuffer) {
Push2Buffer ( false )
}
}
moveChart.focus(chartfilt);
});
});
dc.renderAll();
MyTable = []
MyTable = $('#my-ajax-table').dynatable({
dataset: {
records: Data
},
features: {
pushState: false,
// sort: false //i need to fix the sorting function... the current one just sucks
},
// inputs: {
// queries: $('#searchAB')
// },
writers: {
_rowWriter: ulWriter
// _cellWriter: customCellWriter
}
});
MyTable.data('dynatable').paginationPage.set(1);
MyTable.data('dynatable').process();
if ( $(".imadiv").length>0 ) return 1;
$('<br><br><div class="imadiv"></div>').insertAfter(".dynatable-per-page")
$(".dynatable-record-count").insertAfter(".imadiv")
$(".dynatable-pagination-links").insertAfter(".imadiv")
$("#filter_search").html( $("#filter_search").html().replace('selected="selected"') );
$("#"+SearchFilter).attr( "selected" , "selected" )
var the_content = $("#filter_search").html();
$(""+the_content).insertAfter("#dynatable-query-search-my-ajax-table")
// $('#searchAB').click( function() {
// if($(this).is(':checked')) {
// console.log( "Do stuff")
// $("#dynatable-query-search-my-ajax-table").keyup(function (e) {
// if (e.keyCode == 13) {
// console.log("Do stuff: Just pressed ENTER")
// }
// })
// }
// });
// MyTable.data('dynatable').settings.inputs.queries = { $('#searchAB') }
// .insertAfter("#dynatable-query-search-my-ajax-table")
return "OK"
}
function SearchFilters( elem ) {
var MODE = elem.value;
if( MODE == "filter_all") {
var result = Main_test(AjaxRecords , MODE)
console.log( result )
}
if( MODE == "filter_dupl-titles") {
var getDupl_API = "/api/nodes/"+url_mainIDs["corpus"]+"/children/duplicates?keys=title&limit=9999"
$.ajax({
url: getDupl_API,
success: function(data) {
bisarray = data.data
for(var i in bisarray) {
titlebis = bisarray[i].values
BIS_dict[titlebis[0]] = true;
}
var Duplicates = []
for(var r in AjaxRecords) {
if ( BIS_dict[AjaxRecords[r].title] )
Duplicates.push( AjaxRecords[r] )
}
var result = Main_test(Duplicates , MODE)
console.log( result )
MyTable.data('dynatable').sorts.clear();
MyTable.data('dynatable').sorts.add('title', 1) // 1=ASCENDING,
MyTable.data('dynatable').process();
}
});
}
}
$("#corpusdisplayer").hide()
// FIRST portion of code to be EXECUTED:
// (3) Get records and hyperdata for paginator
$.ajax({
url: '/tests/paginator/corpus/'+url_mainIDs["corpus"],
success: function(data){
$("#content_loader").remove()
for(var i in data.records) {
var orig_id = parseInt(data.records[i].id)
var arr_id = parseInt(i)
RecDict[orig_id] = arr_id;
data.records[i]["title"] = data.records[i]["name"];
data.records[i]["name"] = '<a target="_blank" href="/project/'+url_mainIDs["project"]+'/corpus/'+ url_mainIDs["corpus"] + '/document/'+orig_id+'">'+data.records[i]["name"]+'</a>'
data.records[i]["del"] = false
}
AjaxRecords = data.records; // backup-ing in global variable!
var result = Main_test(data.records , "filter_all")
$("#corpusdisplayer").show()
$("#content_loader").remove()
$("#corpusdisplayer").click()
console.log( result )
},
});
/*
* jQuery Dynatable plugin 0.3.1
*
* Copyright (c) 2014 Steve Schwartz (JangoSteve)
*
* Dual licensed under the AGPL and Proprietary licenses:
* http://www.dynatable.com/license/
*
* Date: Tue Jan 02 2014
*/
//
(function($) {
var defaults,
mergeSettings,
dt,
Model,
modelPrototypes = {
dom: Dom,
domColumns: DomColumns,
records: Records,
recordsCount: RecordsCount,
processingIndicator: ProcessingIndicator,
state: State,
sorts: Sorts,
sortsHeaders: SortsHeaders,
queries: Queries,
inputsSearch: InputsSearch,
paginationPage: PaginationPage,
paginationPerPage: PaginationPerPage,
paginationLinks: PaginationLinks
},
utility,
build,
processAll,
initModel,
defaultRowWriter,
defaultCellWriter,
defaultAttributeWriter,
defaultAttributeReader;
//-----------------------------------------------------------------
// Cached plugin global defaults
//-----------------------------------------------------------------
defaults = {
features: {
paginate: true,
sort: true,
pushState: true,
search: true,
recordCount: true,
perPageSelect: true
},
table: {
defaultColumnIdStyle: 'camelCase',
columns: null,
headRowSelector: 'thead tr', // or e.g. tr:first-child
bodyRowSelector: 'tbody tr',
headRowClass: null,
copyHeaderAlignment: true,
copyHeaderClass: false
},
inputs: {
queries: null,
sorts: null,
multisort: ['ctrlKey', 'shiftKey', 'metaKey'],
page: null,
queryEvent: 'blur change',
recordCountTarget: null,
recordCountPlacement: 'after',
paginationLinkTarget: null,
paginationLinkPlacement: 'after',
paginationClass: 'dynatable-pagination-links',
paginationLinkClass: 'dynatable-page-link',
paginationPrevClass: 'dynatable-page-prev',
paginationNextClass: 'dynatable-page-next',
paginationActiveClass: 'dynatable-active-page',
paginationDisabledClass: 'dynatable-disabled-page',
paginationPrev: 'Previous',
paginationNext: 'Next',
paginationGap: [1,2,2,1],
searchTarget: null,
searchPlacement: 'before',
searchText: 'Search: ',
perPageTarget: null,
perPagePlacement: 'before',
perPageText: 'Show: ',
pageText: 'Pages: ',
recordCountPageBoundTemplate: '{pageLowerBound} to {pageUpperBound} of',
recordCountPageUnboundedTemplate: '{recordsShown} of',
recordCountTotalTemplate: '{recordsQueryCount} {collectionName}',
recordCountFilteredTemplate: ' (filtered from {recordsTotal} total records)',
recordCountText: 'Showing',
recordCountTextTemplate: '{text} {pageTemplate} {totalTemplate} {filteredTemplate}',
recordCountTemplate: '<span id="dynatable-record-count-{elementId}" class="dynatable-record-count">{textTemplate}</span>',
processingText: 'Processing...'
},
dataset: {
ajax: false,
ajaxUrl: null,
ajaxCache: null,
ajaxOnLoad: false,
ajaxMethod: 'GET',
ajaxDataType: 'json',
totalRecordCount: null,
queries: {},
queryRecordCount: null,
page: null,
perPageDefault: 10,
perPageOptions: [10,20,50,100],
sorts: {},
sortsKeys: [],
sortTypes: {},
records: null
},
writers: {
_rowWriter: defaultRowWriter,
_cellWriter: defaultCellWriter,
_attributeWriter: defaultAttributeWriter
},
readers: {
_rowReader: null,
_attributeReader: defaultAttributeReader
},
params: {
dynatable: 'dynatable',
queries: 'queries',
sorts: 'sorts',
page: 'page',
perPage: 'perPage',
offset: 'offset',
records: 'records',
record: null,
queryRecordCount: 'queryRecordCount',
totalRecordCount: 'totalRecordCount'
}
};
//-----------------------------------------------------------------
// Each dynatable instance inherits from this,
// set properties specific to instance
//-----------------------------------------------------------------
dt = {
init: function(element, options) {
this.settings = mergeSettings(options);
this.element = element;
this.$element = $(element);
// All the setup that doesn't require element or options
build.call(this);
return this;
},
process: function(skipPushState) {
processAll.call(this, skipPushState);
}
};
//-----------------------------------------------------------------
// Cached plugin global functions
//-----------------------------------------------------------------
mergeSettings = function(options) {
var newOptions = $.extend(true, {}, defaults, options);
// TODO: figure out a better way to do this.
// Doing `extend(true)` causes any elements that are arrays
// to merge the default and options arrays instead of overriding the defaults.
if (options) {
if (options.inputs) {
if (options.inputs.multisort) {
newOptions.inputs.multisort = options.inputs.multisort;
}
if (options.inputs.paginationGap) {
newOptions.inputs.paginationGap = options.inputs.paginationGap;
}
}
if (options.dataset && options.dataset.perPageOptions) {
newOptions.dataset.perPageOptions = options.dataset.perPageOptions;
}
}
return newOptions;
};
build = function() {
this.$element.trigger('dynatable:preinit', this);
for (model in modelPrototypes) {
if (modelPrototypes.hasOwnProperty(model)) {
var modelInstance = this[model] = new modelPrototypes[model](this, this.settings);
if (modelInstance.initOnLoad()) {
modelInstance.init();
}
}
}
this.$element.trigger('dynatable:init', this);
if (!this.settings.dataset.ajax || (this.settings.dataset.ajax && this.settings.dataset.ajaxOnLoad) || this.settings.features.paginate || (this.settings.features.sort && !$.isEmptyObject(this.settings.dataset.sorts))) {
this.process();
}
};
processAll = function(skipPushState) {
if( $("#multiple_selection").length>0 )
$("#multiple_selection")[0].checked = false;
var data = {};
this.$element.trigger('dynatable:beforeProcess', data);
if (!$.isEmptyObject(this.settings.dataset.queries)) { data[this.settings.params.queries] = this.settings.dataset.queries; }
// TODO: Wrap this in a try/rescue block to hide the processing indicator and indicate something went wrong if error
this.processingIndicator.show();
if (this.settings.features.sort && !$.isEmptyObject(this.settings.dataset.sorts)) { data[this.settings.params.sorts] = this.settings.dataset.sorts; }
if (this.settings.features.paginate && this.settings.dataset.page) {
var page = this.settings.dataset.page,
perPage = this.settings.dataset.perPage;
data[this.settings.params.page] = page;
data[this.settings.params.perPage] = perPage;
data[this.settings.params.offset] = (page - 1) * perPage;
}
if (this.settings.dataset.ajaxData) { $.extend(data, this.settings.dataset.ajaxData); }
// If ajax, sends query to ajaxUrl with queries and sorts serialized and appended in ajax data
// otherwise, executes queries and sorts on in-page data
if (this.settings.dataset.ajax) {
var _this = this;
var options = {
type: _this.settings.dataset.ajaxMethod,
dataType: _this.settings.dataset.ajaxDataType,
data: data,
error: function(xhr, error) {
},
success: function(response) {
_this.$element.trigger('dynatable:ajax:success', response);
// Merge ajax results and meta-data into dynatables cached data
_this.records.updateFromJson(response);
// update table with new records
_this.dom.update();
if (!skipPushState && _this.state.initOnLoad()) {
_this.state.push(data);
}
},
complete: function() {
_this.processingIndicator.hide();
}
};
// Do not pass url to `ajax` options if blank
if (this.settings.dataset.ajaxUrl) {
options.url = this.settings.dataset.ajaxUrl;
// If ajaxUrl is blank, then we're using the current page URL,
// we need to strip out any query, sort, or page data controlled by dynatable
// that may have been in URL when page loaded, so that it doesn't conflict with
// what's passed in with the data ajax parameter
} else {
options.url = utility.refreshQueryString(window.location.href, {}, this.settings);
}
if (this.settings.dataset.ajaxCache !== null) { options.cache = this.settings.dataset.ajaxCache; }
$.ajax(options);
} else {
// console.log("\tthis is very important")
this.records.resetOriginal();
this.queries.run();
if (this.settings.features.sort) {
this.records.sort();
}
if (this.settings.features.paginate) {
this.records.paginate();
}
this.dom.update();
this.processingIndicator.hide();
if (!skipPushState && this.state.initOnLoad()) {
this.state.push(data);
}
}
this.$element.trigger('dynatable:afterProcess', data);
};
function defaultRowWriter(rowIndex, record, columns, cellWriter) {
var tr = '';
// grab the record's attribute for each column
for (var i = 0, len = columns.length; i < len; i++) {
tr += cellWriter(columns[i], record);
}
return '<tr>' + tr + '</tr>';
};
function defaultCellWriter(column, record) {
var html = column.attributeWriter(record),
td = '<td';
// console.log("html:",html)
if (column.hidden || column.textAlign) {
td += ' style="';
// keep cells for hidden column headers hidden
if (column.hidden) {
td += 'display: none;';
}
// keep cells aligned as their column headers are aligned
if (column.textAlign) {
td += 'text-align: ' + column.textAlign + ';';
}
td += '"';
}
if (column.cssClass) {
td += ' class="' + column.cssClass + '"';
}
return td + '>' + html + '</td>';
};
function defaultAttributeWriter(record) {
// `this` is the column object in settings.columns
// TODO: automatically convert common types, such as arrays and objects, to string
return record[this.id];
};
function defaultAttributeReader(cell, record) {
return $(cell).html();
};
//-----------------------------------------------------------------
// Dynatable object model prototype
// (all object models get these default functions)
//-----------------------------------------------------------------
Model = {
initOnLoad: function() {
return true;
},
init: function() {}
};
for (model in modelPrototypes) {
if (modelPrototypes.hasOwnProperty(model)) {
var modelPrototype = modelPrototypes[model];
modelPrototype.prototype = Model;
}
}
//-----------------------------------------------------------------
// Dynatable object models
//-----------------------------------------------------------------
function Dom(obj, settings) {
var _this = this;
// update table contents with new records array
// from query (whether ajax or not)
this.update = function() {
var rows = '',
columns = settings.table.columns,
rowWriter = settings.writers._rowWriter,
cellWriter = settings.writers._cellWriter;
obj.$element.trigger('dynatable:beforeUpdate', rows);
// loop through records
for (var i = 0, len = settings.dataset.records.length; i < len; i++) {
var record = settings.dataset.records[i],
tr = rowWriter(i, record, columns, cellWriter);
// console.log("therecord:")
// console.log(record)
rows += tr;
}
// Appended dynatable interactive elements
if (settings.features.recordCount) {
$('#dynatable-record-count-' + obj.element.id).replaceWith(obj.recordsCount.create());
}
if (settings.features.paginate) {
$('#dynatable-pagination-links-' + obj.element.id).replaceWith(obj.paginationLinks.create());
if (settings.features.perPageSelect) {
$('#dynatable-per-page-' + obj.element.id).val(parseInt(settings.dataset.perPage));
}
}
// Sort headers functionality
if (settings.features.sort && columns) {
obj.sortsHeaders.removeAllArrows();
for (var i = 0, len = columns.length; i < len; i++) {
var column = columns[i],
sortedByColumn = utility.allMatch(settings.dataset.sorts, column.sorts, function(sorts, sort) { return sort in sorts; }),
value = settings.dataset.sorts[column.sorts[0]];
if (sortedByColumn) {
obj.$element.find('[data-dynatable-column="' + column.id + '"]').find('.dynatable-sort-header').each(function(){
if (value == 1) {
obj.sortsHeaders.appendArrowUp($(this));
} else {
obj.sortsHeaders.appendArrowDown($(this));
}
});
}
}
}
// Query search functionality
if (settings.inputs.queries || settings.features.search) {
var allQueries = settings.inputs.queries || $();
if (settings.features.search) {
allQueries = allQueries.add('#dynatable-query-search-' + obj.element.id);
}
allQueries.each(function() {
var $this = $(this),
q = settings.dataset.queries[$this.data('dynatable-query')];
$this.val(q || '');
});
}
obj.$element.find(settings.table.bodyRowSelector).remove();
obj.$element.append(rows);
obj.$element.trigger('dynatable:afterUpdate', rows);
};
};
function DomColumns(obj, settings) {
var _this = this;
this.initOnLoad = function() {
return obj.$element.is('table');
};
this.init = function() {
settings.table.columns = [];
this.getFromTable();
};
// initialize table[columns] array
this.getFromTable = function() {
var $columns = obj.$element.find(settings.table.headRowSelector).children('th,td');
if ($columns.length) {
$columns.each(function(index){
_this.add($(this), index, true);
});
} else {
return $.error("Couldn't find any columns headers in '" + settings.table.headRowSelector + " th,td'. If your header row is different, specify the selector in the table: headRowSelector option.");
}
};
this.add = function($column, position, skipAppend, skipUpdate) {
var columns = settings.table.columns,
label = $column.text(),
id = $column.data('dynatable-column') || utility.normalizeText(label, settings.table.defaultColumnIdStyle),
dataSorts = $column.data('dynatable-sorts'),
sorts = dataSorts ? $.map(dataSorts.split(','), function(text) { return $.trim(text); }) : [id];
// If the column id is blank, generate an id for it
if ( !id ) {
this.generate($column);
id = $column.data('dynatable-column');
}
// Add column data to plugin instance
columns.splice(position, 0, {
index: position,
label: label,
id: id,
attributeWriter: settings.writers[id] || settings.writers._attributeWriter,
attributeReader: settings.readers[id] || settings.readers._attributeReader,
sorts: sorts,
hidden: $column.css('display') === 'none',
textAlign: settings.table.copyHeaderAlignment && $column.css('text-align'),
cssClass: settings.table.copyHeaderClass && $column.attr('class')
});
// Modify header cell
$column
.attr('data-dynatable-column', id)
.addClass('dynatable-head');
if (settings.table.headRowClass) { $column.addClass(settings.table.headRowClass); }
// Append column header to table
if (!skipAppend) {
var domPosition = position + 1,
$sibling = obj.$element.find(settings.table.headRowSelector)
.children('th:nth-child(' + domPosition + '),td:nth-child(' + domPosition + ')').first(),
columnsAfter = columns.slice(position + 1, columns.length);
if ($sibling.length) {
$sibling.before($column);
// sibling column doesn't yet exist (maybe this is the last column in the header row)
} else {
obj.$element.find(settings.table.headRowSelector).append($column);
}
obj.sortsHeaders.attachOne($column.get());
// increment the index of all columns after this one that was just inserted
if (columnsAfter.length) {
for (var i = 0, len = columnsAfter.length; i < len; i++) {
columnsAfter[i].index += 1;
}
}
if (!skipUpdate) {
obj.dom.update();
}
}
return dt;
};
this.remove = function(columnIndexOrId) {
var columns = settings.table.columns,
length = columns.length;
if (typeof(columnIndexOrId) === "number") {
var column = columns[columnIndexOrId];
this.removeFromTable(column.id);
this.removeFromArray(columnIndexOrId);
} else {
// Traverse columns array in reverse order so that subsequent indices
// don't get messed up when we delete an item from the array in an iteration
for (var i = columns.length - 1; i >= 0; i--) {
var column = columns[i];
if (column.id === columnIndexOrId) {
this.removeFromTable(columnIndexOrId);
this.removeFromArray(i);
}
}
}
obj.dom.update();
};
this.removeFromTable = function(columnId) {
obj.$element.find(settings.table.headRowSelector).children('[data-dynatable-column="' + columnId + '"]').first()
.remove();
};
this.removeFromArray = function(index) {
var columns = settings.table.columns,
adjustColumns;
columns.splice(index, 1);
adjustColumns = columns.slice(index, columns.length);
for (var i = 0, len = adjustColumns.length; i < len; i++) {
adjustColumns[i].index -= 1;
}
};
this.generate = function($cell) {
var cell = $cell === undefined ? $('<th></th>') : $cell;
return this.attachGeneratedAttributes(cell);
};
this.attachGeneratedAttributes = function($cell) {
// Use increment to create unique column name that is the same each time the page is reloaded,
// in order to avoid errors with mismatched attribute names when loading cached `dataset.records` array
var increment = obj.$element.find(settings.table.headRowSelector).children('th[data-dynatable-generated]').length;
return $cell
.attr('data-dynatable-column', 'dynatable-generated-' + increment) //+ utility.randomHash(),
.attr('data-dynatable-no-sort', 'true')
.attr('data-dynatable-generated', increment);
};
};
function Records(obj, settings) {
var _this = this;
this.initOnLoad = function() {
return !settings.dataset.ajax;
};
this.init = function() {
if (settings.dataset.records === null) {
settings.dataset.records = this.getFromTable();
if (!settings.dataset.queryRecordCount) {
settings.dataset.queryRecordCount = this.count();
}
if (!settings.dataset.totalRecordCount){
settings.dataset.totalRecordCount = settings.dataset.queryRecordCount;
}
}
// Create cache of original full recordset (unpaginated and unqueried)
settings.dataset.originalRecords = $.extend(true, [], settings.dataset.records);
};
// merge ajax response json with cached data including
// meta-data and records
this.updateFromJson = function(data) {
var records;
if (settings.params.records === "_root") {
records = data;
} else if (settings.params.records in data) {
records = data[settings.params.records];
}
if (settings.params.record) {
var len = records.length - 1;
for (var i = 0; i < len; i++) {
records[i] = records[i][settings.params.record];
}
}
if (settings.params.queryRecordCount in data) {
settings.dataset.queryRecordCount = data[settings.params.queryRecordCount];
}
if (settings.params.totalRecordCount in data) {
settings.dataset.totalRecordCount = data[settings.params.totalRecordCount];
}
settings.dataset.records = records;
};
// For really advanced sorting,
// see http://james.padolsey.com/javascript/sorting-elements-with-jquery/
this.sort = function() {
var sort = [].sort;
var sorts = settings.dataset.sorts;
var sortsKeys = settings.dataset.sortsKeys;
var sortTypes = settings.dataset.sortTypes;
var sortFunction = function(a, b) {
var comparison;
if ($.isEmptyObject(sorts)) {
comparison = obj.sorts.functions['originalPlacement'](a, b);
} else {
// // console.log( $.isEmptyObject(sorts) )
// // console.log(sortsKeys)
// console.log("( "+a.name+"|"+a.score+" , "+b.name+"|"+a.score+" )")
for (var i = 0, len = sortsKeys.length; i < len; i++) {
var attr = sortsKeys[i];
var direction = sorts[attr];
var sortType = sortTypes[attr] || obj.sorts.guessType(a, b, attr);
var comparison = obj.sorts.functions[sortType](a, b, attr, direction);
// // console.log("a: "+a+" | b: "+b)
// console.log("\tattr: "+attr+" | direction: "+direction+" | sortType: "+sortType+" | comparison: "+comparison)
// // Don't need to sort any further unless this sort is a tie between a and b,
// // so break the for loop unless tied
if (comparison !== 0) { break; }
}
// console.log(" - -- - - - ")
}
return comparison;
}
// // var thearray = sort.call(settings.dataset.records, sortFunction)
// var thearray = settings.dataset.records
// for(var i in thearray) {
// console.log(i+": "+thearray[i].date)
// }
return sort.call(settings.dataset.records, sortFunction);
// return settings.dataset.records
};
this.paginate = function() {
var bounds = this.pageBounds(),
first = bounds[0], last = bounds[1];
settings.dataset.records = settings.dataset.records.slice(first, last);
};
this.resetOriginal = function() {
settings.dataset.records = settings.dataset.originalRecords || [];
};
this.pageBounds = function() {
var page = settings.dataset.page || 1,
first = (page - 1) * settings.dataset.perPage,
last = Math.min(first + settings.dataset.perPage, settings.dataset.queryRecordCount);
return [first,last];
};
// get initial recordset to populate table
// if ajax, call ajaxUrl
// otherwise, initialize from in-table records
this.getFromTable = function() {
var records = [],
columns = settings.table.columns,
tableRecords = obj.$element.find(settings.table.bodyRowSelector);
tableRecords.each(function(index){
var record = {};
record['dynatable-original-index'] = index;
$(this).find('th,td').each(function(index) {
if (columns[index] === undefined) {
// Header cell didn't exist for this column, so let's generate and append
// a new header cell with a randomly generated name (so we can store and
// retrieve the contents of this column for each record)
obj.domColumns.add(obj.domColumns.generate(), columns.length, false, true); // don't skipAppend, do skipUpdate
}
var value = columns[index].attributeReader(this, record),
attr = columns[index].id;
// If value from table is HTML, let's get and cache the text equivalent for
// the default string sorting, since it rarely makes sense for sort headers
// to sort based on HTML tags.
if (typeof(value) === "string" && value.match(/\s*\<.+\>/)) {
if (! record['dynatable-sortable-text']) {
record['dynatable-sortable-text'] = {};
}
record['dynatable-sortable-text'][attr] = $.trim($('<div></div>').html(value).text());
}
record[attr] = value;
});
// Allow configuration function which alters record based on attributes of
// table row (e.g. from html5 data- attributes)
if (typeof(settings.readers._rowReader) === "function") {
settings.readers._rowReader(index, this, record);
}
records.push(record);
});
return records; // 1st row is header
};
// count records from table
this.count = function() {
return settings.dataset.records.length;
};
};
function RecordsCount(obj, settings) {
this.initOnLoad = function() {
return settings.features.recordCount;
};
this.init = function() {
this.attach();
};
this.create = function() {
var pageTemplate = '',
filteredTemplate = '',
options = {
elementId: obj.element.id,
recordsShown: obj.records.count(),
recordsQueryCount: settings.dataset.queryRecordCount,
recordsTotal: settings.dataset.totalRecordCount,
collectionName: settings.params.records === "_root" ? "records" : settings.params.records,
text: settings.inputs.recordCountText
};
if (settings.features.paginate) {
// If currently displayed records are a subset (page) of the entire collection
if (options.recordsShown < options.recordsQueryCount) {
var bounds = obj.records.pageBounds();
options.pageLowerBound = bounds[0] + 1;
options.pageUpperBound = bounds[1];
pageTemplate = settings.inputs.recordCountPageBoundTemplate;
// Else if currently displayed records are the entire collection
} else if (options.recordsShown === options.recordsQueryCount) {
pageTemplate = settings.inputs.recordCountPageUnboundedTemplate;
}
}
// If collection for table is queried subset of collection
if (options.recordsQueryCount < options.recordsTotal) {
filteredTemplate = settings.inputs.recordCountFilteredTemplate;
}
// Populate templates with options
options.pageTemplate = utility.template(pageTemplate, options);
options.filteredTemplate = utility.template(filteredTemplate, options);
options.totalTemplate = utility.template(settings.inputs.recordCountTotalTemplate, options);
options.textTemplate = utility.template(settings.inputs.recordCountTextTemplate, options);
return utility.template(settings.inputs.recordCountTemplate, options);
};
this.attach = function() {
var $target = settings.inputs.recordCountTarget ? $(settings.inputs.recordCountTarget) : obj.$element;
$target[settings.inputs.recordCountPlacement](this.create());
};
};
function ProcessingIndicator(obj, settings) {
this.init = function() {
this.attach();
};
this.create = function() {
var $processing = $('<div></div>', {
html: '<span>' + settings.inputs.processingText + '</span>',
id: 'dynatable-processing-' + obj.element.id,
'class': 'dynatable-processing',
style: 'position: absolute; display: none;'
});
return $processing;
};
this.position = function() {
var $processing = $('#dynatable-processing-' + obj.element.id),
$span = $processing.children('span'),
spanHeight = $span.outerHeight(),
spanWidth = $span.outerWidth(),
$covered = obj.$element,
offset = $covered.offset(),
height = $covered.outerHeight(), width = $covered.outerWidth();
$processing
.offset({left: offset.left, top: offset.top})
.width(width)
.height(height)
$span
.offset({left: offset.left + ( (width - spanWidth) / 2 ), top: offset.top + ( (height - spanHeight) / 2 )});
return $processing;
};
this.attach = function() {
obj.$element.before(this.create());
};
this.show = function() {
$('#dynatable-processing-' + obj.element.id).show();
this.position();
};
this.hide = function() {
$('#dynatable-processing-' + obj.element.id).hide();
};
};
function State(obj, settings) {
this.initOnLoad = function() {
// Check if pushState option is true, and if browser supports it
return settings.features.pushState && history.pushState;
};
this.init = function() {
window.onpopstate = function(event) {
if (event.state && event.state.dynatable) {
obj.state.pop(event);
}
}
};
this.push = function(data) {
var urlString = window.location.search,
urlOptions,
path,
params,
hash,
newParams,
cacheStr,
cache,
// replaceState on initial load, then pushState after that
firstPush = !(window.history.state && window.history.state.dynatable),
pushFunction = firstPush ? 'replaceState' : 'pushState';
if (urlString && /^\?/.test(urlString)) { urlString = urlString.substring(1); }
$.extend(urlOptions, data);
params = utility.refreshQueryString(urlString, data, settings);
if (params) { params = '?' + params; }
hash = window.location.hash;
path = window.location.pathname;
obj.$element.trigger('dynatable:push', data);
cache = { dynatable: { dataset: settings.dataset } };
if (!firstPush) { cache.dynatable.scrollTop = $(window).scrollTop(); }
cacheStr = JSON.stringify(cache);
// Mozilla has a 640k char limit on what can be stored in pushState.
// See "limit" in https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history#The_pushState().C2.A0method
// and "dataStr.length" in http://wine.git.sourceforge.net/git/gitweb.cgi?p=wine/wine-gecko;a=patch;h=43a11bdddc5fc1ff102278a120be66a7b90afe28
//
// Likewise, other browsers may have varying (undocumented) limits.
// Also, Firefox's limit can be changed in about:config as browser.history.maxStateObjectSize
// Since we don't know what the actual limit will be in any given situation, we'll just try caching and rescue
// any exceptions by retrying pushState without caching the records.
//
// I have absolutely no idea why perPageOptions suddenly becomes an array-like object instead of an array,
// but just recently, this started throwing an error if I don't convert it:
// 'Uncaught Error: DATA_CLONE_ERR: DOM Exception 25'
cache.dynatable.dataset.perPageOptions = $.makeArray(cache.dynatable.dataset.perPageOptions);
try {
window.history[pushFunction](cache, "Dynatable state", path + params + hash);
} catch(error) {
// Make cached records = null, so that `pop` will rerun process to retrieve records
cache.dynatable.dataset.records = null;
window.history[pushFunction](cache, "Dynatable state", path + params + hash);
}
};
this.pop = function(event) {
var data = event.state.dynatable;
settings.dataset = data.dataset;
if (data.scrollTop) { $(window).scrollTop(data.scrollTop); }
// If dataset.records is cached from pushState
if ( data.dataset.records ) {
obj.dom.update();
} else {
obj.process(true);
}
};
};
function Sorts(obj, settings) {
this.initOnLoad = function() {
return settings.features.sort;
};
this.init = function() {
var sortsUrl = window.location.search.match(new RegExp(settings.params.sorts + '[^&=]*=[^&]*', 'g'));
if (sortsUrl) {
settings.dataset.sorts = utility.deserialize(sortsUrl)[settings.params.sorts];
}
if (!settings.dataset.sortsKeys.length) {
settings.dataset.sortsKeys = utility.keysFromObject(settings.dataset.sorts);
}
};
this.add = function(attr, direction) {
var sortsKeys = settings.dataset.sortsKeys,
index = $.inArray(attr, sortsKeys);
settings.dataset.sorts[attr] = direction;
obj.$element.trigger('dynatable:sorts:added', [attr, direction]);
if (index === -1) { sortsKeys.push(attr); }
return dt;
};
this.remove = function(attr) {
var sortsKeys = settings.dataset.sortsKeys,
index = $.inArray(attr, sortsKeys);
delete settings.dataset.sorts[attr];
obj.$element.trigger('dynatable:sorts:removed', attr);
if (index !== -1) { sortsKeys.splice(index, 1); }
return dt;
};
this.clear = function() {
settings.dataset.sorts = {};
settings.dataset.sortsKeys.length = 0;
settings.dataset.queries = {};
obj.$element.trigger('dynatable:sorts:cleared');
};
// Try to intelligently guess which sort function to use
// based on the type of attribute values.
// Consider using something more robust than `typeof` (http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/)
this.guessType = function(a, b, attr) {
var types = {
string: 'string',
number: 'number',
'boolean': 'number',
object: 'number' // dates and null values are also objects, this works...
},
attrType = a[attr] ? typeof(a[attr]) : typeof(b[attr]),
type = types[attrType] || 'number';
return type;
};
// Built-in sort functions
// (the most common use-cases I could think of)
this.functions = {
number: function(a, b, attr, direction) {
return a[attr] === b[attr] ? 0 : (direction > 0 ? a[attr] - b[attr] : b[attr] - a[attr]);
},
string: function(a, b, attr, direction) {
var aAttr = (a['dynatable-sortable-text'] && a['dynatable-sortable-text'][attr]) ? a['dynatable-sortable-text'][attr] : a[attr],
bAttr = (b['dynatable-sortable-text'] && b['dynatable-sortable-text'][attr]) ? b['dynatable-sortable-text'][attr] : b[attr],
comparison;
aAttr = aAttr.toLowerCase();
bAttr = bAttr.toLowerCase();
comparison = aAttr === bAttr ? 0 : (direction > 0 ? aAttr > bAttr : bAttr > aAttr);
// force false boolean value to -1, true to 1, and tie to 0
return comparison === false ? -1 : (comparison - 0);
},
originalPlacement: function(a, b) {
return a['dynatable-original-index'] - b['dynatable-original-index'];
}
};
};
// turn table headers into links which add sort to sorts array
function SortsHeaders(obj, settings) {
var _this = this;
this.initOnLoad = function() {
return settings.features.sort;
};
this.init = function() {
this.attach();
};
this.create = function(cell) {
var $cell = $(cell),
$link = $('<a></a>', {
'class': 'dynatable-sort-header',
href: '#',
html: $cell.html()
}),
id = $cell.data('dynatable-column'),
column = utility.findObjectInArray(settings.table.columns, {id: id});
$link.bind('click', function(e) {
_this.toggleSort(e, $link, column);
obj.process();
e.preventDefault();
});
if (this.sortedByColumn($link, column)) {
if (this.sortedByColumnValue(column) == 1) {
this.appendArrowUp($link);
} else {
this.appendArrowDown($link);
}
}
return $link;
};
this.removeAll = function() {
obj.$element.find(settings.table.headRowSelector).children('th,td').each(function(){
_this.removeAllArrows();
_this.removeOne(this);
});
};
this.removeOne = function(cell) {
var $cell = $(cell),
$link = $cell.find('.dynatable-sort-header');
if ($link.length) {
var html = $link.html();
$link.remove();
$cell.html($cell.html() + html);
}
};
this.attach = function() {
obj.$element.find(settings.table.headRowSelector).children('th,td').each(function(){
_this.attachOne(this);
});
};
this.attachOne = function(cell) {
var $cell = $(cell);
if (!$cell.data('dynatable-no-sort')) {
$cell.html(this.create(cell));
}
};
this.appendArrowUp = function($link) {
this.removeArrow($link);
$link.append("<span class='dynatable-arrow'> &#9650;</span>");
};
this.appendArrowDown = function($link) {
this.removeArrow($link);
$link.append("<span class='dynatable-arrow'> &#9660;</span>");
};
this.removeArrow = function($link) {
// Not sure why `parent()` is needed, the arrow should be inside the link from `append()` above
$link.find('.dynatable-arrow').remove();
};
this.removeAllArrows = function() {
obj.$element.find('.dynatable-arrow').remove();
};
this.toggleSort = function(e, $link, column) {
var sortedByColumn = this.sortedByColumn($link, column),
value = this.sortedByColumnValue(column);
// Clear existing sorts unless this is a multisort event
if (!settings.inputs.multisort || !utility.anyMatch(e, settings.inputs.multisort, function(evt, key) { return e[key]; })) {
this.removeAllArrows();
obj.sorts.clear();
}
// If sorts for this column are already set
if (sortedByColumn) {
// If ascending, then make descending
if (value == 1) {
for (var i = 0, len = column.sorts.length; i < len; i++) {
obj.sorts.add(column.sorts[i], -1);
}
this.appendArrowDown($link);
// If descending, remove sort
} else {
for (var i = 0, len = column.sorts.length; i < len; i++) {
obj.sorts.remove(column.sorts[i]);
}
this.removeArrow($link);
}
// Otherwise, if not already set, set to ascending
} else {
for (var i = 0, len = column.sorts.length; i < len; i++) {
obj.sorts.add(column.sorts[i], 1);
}
this.appendArrowUp($link);
}
};
this.sortedByColumn = function($link, column) {
return utility.allMatch(settings.dataset.sorts, column.sorts, function(sorts, sort) { return sort in sorts; });
};
this.sortedByColumnValue = function(column) {
return settings.dataset.sorts[column.sorts[0]];
};
};
function Queries(obj, settings) {
var _this = this;
this.initOnLoad = function() {
return settings.inputs.queries || settings.features.search;
};
this.init = function() {
var queriesUrl = window.location.search.match(new RegExp(settings.params.queries + '[^&=]*=[^&]*', 'g'));
settings.dataset.queries = queriesUrl ? utility.deserialize(queriesUrl)[settings.params.queries] : {};
if (settings.dataset.queries === "") { settings.dataset.queries = {}; }
if (settings.inputs.queries) {
this.setupInputs();
}
};
this.add = function(name, value) {
// reset to first page since query will change records
if (settings.features.paginate) {
settings.dataset.page = 1;
}
settings.dataset.queries[name] = value;
obj.$element.trigger('dynatable:queries:added', [name, value]);
return dt;
};
this.remove = function(name) {
delete settings.dataset.queries[name];
obj.$element.trigger('dynatable:queries:removed', name);
return dt;
};
this.run = function() {
for (query in settings.dataset.queries) {
if (settings.dataset.queries.hasOwnProperty(query)) {
var value = settings.dataset.queries[query];
if (_this.functions[query] === undefined) {
// Try to lazily evaluate query from column names if not explicitly defined
var queryColumn = utility.findObjectInArray(settings.table.columns, {id: query});
if (queryColumn) {
_this.functions[query] = function(record, queryValue) {
return record[query] == queryValue;
};
} else {
$.error("Query named '" + query + "' called, but not defined in queries.functions");
continue; // to skip to next query
}
}
// collect all records that return true for query
if($('input[name=searchAB]:checked').length==0) {
settings.dataset.records = $.map(settings.dataset.records, function(record) {
return _this.functions[query](record, value) ? record : null;
});
} else {
var pageurl = window.location.href.split("/")
var cid;
for(var i in pageurl) {
if(pageurl[i]=="corpus") {
cid=parseInt(i);
break;
}
}
var corpus_id = pageurl[cid+1];
var search_api = window.location.origin+"/v1.0/nodes/"+corpus_id+"/children/ids?limit="+(settings.dataset.records.length)+"&contain="+encodeURI(value.toLowerCase())
var coincidences_ = []
$.ajax({
type: "GET",
url: search_api,
dataType: "json",
async: false,
success : function(data, textStatus, jqXHR) {
var results_ = {}
if(data.pagination.total>0) {
for(var i in data.data) {
results_[data.data[i].id]=true
}
for(var i in settings.dataset.records) {
if( results_[settings.dataset.records[i].id] ) {
coincidences_.push( settings.dataset.records[i] )
}
}
}
},
error: function(exception) {
console.log("error in abstracts-API:");
console.log(exception)
console.log(" - - - -- - - -")
}
})
console.log( "abstracts-API: "+coincidences_.length)
settings.dataset.records = coincidences_
}
}
}
settings.dataset.queryRecordCount = obj.records.count();
};
// Shortcut for performing simple query from built-in search
this.runSearch = function(q) {
var origQueries = $.extend({}, settings.dataset.queries);
if (q) {
this.add('search', q);
} else {
this.remove('search');
}
if (!utility.objectsEqual(settings.dataset.queries, origQueries)) {
obj.process();
}
};
this.setupInputs = function() {
settings.inputs.queries.each(function() {
var $this = $(this),
event = $this.data('dynatable-query-event') || settings.inputs.queryEvent,
query = $this.data('dynatable-query') || $this.attr('name') || this.id,
queryFunction = function(e) {
var q = $(this).val();
if (q === "") { q = undefined; }
if (q === settings.dataset.queries[query]) { return false; }
if (q) {
_this.add(query, q);
} else {
_this.remove(query);
}
obj.process();
e.preventDefault();
};
$this
.attr('data-dynatable-query', query)
.bind(event, queryFunction)
.bind('keypress', function(e) {
if (e.which == 13) {
queryFunction.call(this, e);
}
});
if (settings.dataset.queries[query]) { $this.val(decodeURIComponent(settings.dataset.queries[query])); }
});
};
// Query functions for in-page querying
// each function should take a record and a value as input
// and output true of false as to whether the record is a match or not
this.functions = {
search: function(record, queryValue) {
var contains = false;
// Loop through each attribute of record
for (attr in record) {
if (record.hasOwnProperty(attr)) {
var attrValue = record[attr];
if (typeof(attrValue) === "string" && attrValue.toLowerCase().indexOf(queryValue.toLowerCase()) !== -1) {
contains = true;
// Don't need to keep searching attributes once found
break;
} else {
continue;
}
}
}
return contains;
}
};
};
function InputsSearch(obj, settings) {
var _this = this;
this.initOnLoad = function() {
return settings.features.search;
};
this.init = function() {
this.attach();
};
this.create = function() {
var $search = $('<input />', {
type: 'search',
id: 'dynatable-query-search-' + obj.element.id,
'data-dynatable-query': 'search',
value: settings.dataset.queries.search
}),
$searchSpan = $('<span></span>', {
id: 'dynatable-search-' + obj.element.id,
'class': 'dynatable-search',
text: settings.inputs.searchText
}).append($search);
$search
.bind(settings.inputs.queryEvent, function() {
obj.queries.runSearch($(this).val());
})
.bind('keypress', function(e) {
if (e.which == 13) {
obj.queries.runSearch($(this).val());
e.preventDefault();
}
});
return $searchSpan;
};
this.attach = function() {
var $target = settings.inputs.searchTarget ? $(settings.inputs.searchTarget) : obj.$element;
$target[settings.inputs.searchPlacement](this.create());
};
};
// provide a public function for selecting page
function PaginationPage(obj, settings) {
this.initOnLoad = function() {
return settings.features.paginate;
};
this.init = function() {
var pageUrl = window.location.search.match(new RegExp(settings.params.page + '=([^&]*)'));
// If page is present in URL parameters and pushState is enabled
// (meaning that it'd be possible for dynatable to have put the
// page parameter in the URL)
if (pageUrl && settings.features.pushState) {
this.set(pageUrl[1]);
} else {
this.set(1);
}
};
this.set = function(page) {
var newPage = parseInt(page, 10);
settings.dataset.page = newPage;
obj.$element.trigger('dynatable:page:set', newPage);
}
};
function PaginationPerPage(obj, settings) {
var _this = this;
this.initOnLoad = function() {
return settings.features.paginate;
};
this.init = function() {
var perPageUrl = window.location.search.match(new RegExp(settings.params.perPage + '=([^&]*)'));
// If perPage is present in URL parameters and pushState is enabled
// (meaning that it'd be possible for dynatable to have put the
// perPage parameter in the URL)
if (perPageUrl && settings.features.pushState) {
// Don't reset page to 1 on init, since it might override page
// set on init from URL
this.set(perPageUrl[1], true);
} else {
this.set(settings.dataset.perPageDefault, true);
}
if (settings.features.perPageSelect) {
this.attach();
}
};
this.create = function() {
var $select = $('<select>', {
id: 'dynatable-per-page-' + obj.element.id,
'class': 'dynatable-per-page-select'
});
for (var i = 0, len = settings.dataset.perPageOptions.length; i < len; i++) {
var number = settings.dataset.perPageOptions[i],
selected = settings.dataset.perPage == number ? 'selected="selected"' : '';
$select.append('<option value="' + number + '" ' + selected + '>' + number + '</option>');
}
$select.bind('change', function(e) {
_this.set($(this).val());
obj.process();
});
return $('<span />', {
'class': 'dynatable-per-page'
}).append("<span class='dynatable-per-page-label'>" + settings.inputs.perPageText + "</span>").append($select);
};
this.attach = function() {
var $target = settings.inputs.perPageTarget ? $(settings.inputs.perPageTarget) : obj.$element;
$target[settings.inputs.perPagePlacement](this.create());
};
this.set = function(number, skipResetPage) {
var newPerPage = parseInt(number);
if (!skipResetPage) { obj.paginationPage.set(1); }
settings.dataset.perPage = newPerPage;
obj.$element.trigger('dynatable:perPage:set', newPerPage);
};
};
// pagination links which update dataset.page attribute
function PaginationLinks(obj, settings) {
var _this = this;
this.initOnLoad = function() {
return settings.features.paginate;
};
this.init = function() {
this.attach();
};
this.create = function() {
var pageLinks = '<ul id="' + 'dynatable-pagination-links-' + obj.element.id + '" class="' + settings.inputs.paginationClass + '">',
pageLinkClass = settings.inputs.paginationLinkClass,
activePageClass = settings.inputs.paginationActiveClass,
disabledPageClass = settings.inputs.paginationDisabledClass,
pages = Math.ceil(settings.dataset.queryRecordCount / settings.dataset.perPage),
page = settings.dataset.page,
breaks = [
settings.inputs.paginationGap[0],
settings.dataset.page - settings.inputs.paginationGap[1],
settings.dataset.page + settings.inputs.paginationGap[2],
(pages + 1) - settings.inputs.paginationGap[3]
];
pageLinks += '<li><span>' + settings.inputs.pageText + '</span></li>';
for (var i = 1; i <= pages; i++) {
if ( (i > breaks[0] && i < breaks[1]) || (i > breaks[2] && i < breaks[3])) {
// skip to next iteration in loop
continue;
} else {
var li = obj.paginationLinks.buildLink(i, i, pageLinkClass, page == i, activePageClass),
breakIndex,
nextBreak;
// If i is not between one of the following
// (1 + (settings.paginationGap[0]))
// (page - settings.paginationGap[1])
// (page + settings.paginationGap[2])
// (pages - settings.paginationGap[3])
breakIndex = $.inArray(i, breaks);
nextBreak = breaks[breakIndex + 1];
if (breakIndex > 0 && i !== 1 && nextBreak && nextBreak > (i + 1)) {
var ellip = '<li><span class="dynatable-page-break">&hellip;</span></li>';
li = breakIndex < 2 ? ellip + li : li + ellip;
}
if (settings.inputs.paginationPrev && i === 1) {
var prevLi = obj.paginationLinks.buildLink(page - 1, settings.inputs.paginationPrev, pageLinkClass + ' ' + settings.inputs.paginationPrevClass, page === 1, disabledPageClass);
li = prevLi + li;
}
if (settings.inputs.paginationNext && i === pages) {
var nextLi = obj.paginationLinks.buildLink(page + 1, settings.inputs.paginationNext, pageLinkClass + ' ' + settings.inputs.paginationNextClass, page === pages, disabledPageClass);
li += nextLi;
}
pageLinks += li;
}
}
pageLinks += '</ul>';
// only bind page handler to non-active and non-disabled page links
var selector = '#dynatable-pagination-links-' + obj.element.id + ' a.' + pageLinkClass + ':not(.' + activePageClass + ',.' + disabledPageClass + ')';
// kill any existing delegated-bindings so they don't stack up
$(document).undelegate(selector, 'click.dynatable');
$(document).delegate(selector, 'click.dynatable', function(e) {
$this = $(this);
$this.closest(settings.inputs.paginationClass).find('.' + activePageClass).removeClass(activePageClass);
$this.addClass(activePageClass);
obj.paginationPage.set($this.data('dynatable-page'));
obj.process();
e.preventDefault();
});
return pageLinks;
};
this.buildLink = function(page, label, linkClass, conditional, conditionalClass) {
var link = '<a data-dynatable-page=' + page + ' class="' + linkClass,
li = '<li';
if (conditional) {
link += ' ' + conditionalClass;
li += ' class="' + conditionalClass + '"';
}
link += '">' + label + '</a>';
li += '>' + link + '</li>';
return li;
};
this.attach = function() {
// append page links *after* delegate-event-binding so it doesn't need to
// find and select all page links to bind event
var $target = settings.inputs.paginationLinkTarget ? $(settings.inputs.paginationLinkTarget) : obj.$element;
$target[settings.inputs.paginationLinkPlacement](obj.paginationLinks.create());
};
};
utility = dt.utility = {
normalizeText: function(text, style) {
text = this.textTransform[style](text);
return text;
},
textTransform: {
trimDash: function(text) {
return text.replace(/^\s+|\s+$/g, "").replace(/\s+/g, "-");
},
camelCase: function(text) {
text = this.trimDash(text);
return text
.replace(/(\-[a-zA-Z])/g, function($1){return $1.toUpperCase().replace('-','');})
.replace(/([A-Z])([A-Z]+)/g, function($1,$2,$3){return $2 + $3.toLowerCase();})
.replace(/^[A-Z]/, function($1){return $1.toLowerCase();});
},
dashed: function(text) {
text = this.trimDash(text);
return this.lowercase(text);
},
underscore: function(text) {
text = this.trimDash(text);
return this.lowercase(text.replace(/(-)/g, '_'));
},
lowercase: function(text) {
return text.replace(/([A-Z])/g, function($1){return $1.toLowerCase();});
}
},
// Deserialize params in URL to object
// see http://stackoverflow.com/questions/1131630/javascript-jquery-param-inverse-function/3401265#3401265
deserialize: function(query) {
if (!query) return {};
// modified to accept an array of partial URL strings
if (typeof(query) === "object") { query = query.join('&'); }
var hash = {},
vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("="),
k = decodeURIComponent(pair[0]),
v, m;
if (!pair[1]) { continue };
v = decodeURIComponent(pair[1].replace(/\+/g, ' '));
// modified to parse multi-level parameters (e.g. "hi[there][dude]=whatsup" => hi: {there: {dude: "whatsup"}})
while (m = k.match(/([^&=]+)\[([^&=]+)\]$/)) {
var origV = v;
k = m[1];
v = {};
// If nested param ends in '][', then the regex above erroneously included half of a trailing '[]',
// which indicates the end-value is part of an array
if (m[2].substr(m[2].length-2) == '][') { // must use substr for IE to understand it
v[m[2].substr(0,m[2].length-2)] = [origV];
} else {
v[m[2]] = origV;
}
}
// If it is the first entry with this name
if (typeof hash[k] === "undefined") {
if (k.substr(k.length-2) != '[]') { // not end with []. cannot use negative index as IE doesn't understand it
hash[k] = v;
} else {
hash[k] = [v];
}
// If subsequent entry with this name and not array
} else if (typeof hash[k] === "string") {
hash[k] = v; // replace it
// modified to add support for objects
} else if (typeof hash[k] === "object") {
hash[k] = $.extend({}, hash[k], v);
// If subsequent entry with this name and is array
} else {
hash[k].push(v);
}
}
return hash;
},
refreshQueryString: function(urlString, data, settings) {
var _this = this,
queryString = urlString.split('?'),
path = queryString.shift(),
urlOptions;
urlOptions = this.deserialize(urlString);
// Loop through each dynatable param and update the URL with it
for (attr in settings.params) {
if (settings.params.hasOwnProperty(attr)) {
var label = settings.params[attr];
// Skip over parameters matching attributes for disabled features (i.e. leave them untouched),
// because if the feature is turned off, then parameter name is a coincidence and it's unrelated to dynatable.
if (
(!settings.features.sort && attr == "sorts") ||
(!settings.features.paginate && _this.anyMatch(attr, ["page", "perPage", "offset"], function(attr, param) { return attr == param; }))
) {
continue;
}
// Delete page and offset from url params if on page 1 (default)
if ((attr === "page" || attr === "offset") && data["page"] === 1) {
if (urlOptions[label]) {
delete urlOptions[label];
}
continue;
}
// Delete perPage from url params if default perPage value
if (attr === "perPage" && data[label] == settings.dataset.perPageDefault) {
if (urlOptions[label]) {
delete urlOptions[label];
}
continue;
}
// For queries, we're going to handle each possible query parameter individually here instead of
// handling the entire queries object below, since we need to make sure that this is a query controlled by dynatable.
if (attr == "queries" && data[label]) {
var queries = settings.inputs.queries || [],
inputQueries = $.makeArray(queries.map(function() { return $(this).attr('name') }));
if (settings.features.search) { inputQueries.push('search'); }
for (var i = 0, len = inputQueries.length; i < len; i++) {
var attr = inputQueries[i];
if (data[label][attr]) {
if (typeof urlOptions[label] === 'undefined') { urlOptions[label] = {}; }
urlOptions[label][attr] = data[label][attr];
} else {
delete urlOptions[label][attr];
}
}
continue;
}
// If we haven't returned true by now, then we actually want to update the parameter in the URL
if (data[label]) {
urlOptions[label] = data[label];
} else {
delete urlOptions[label];
}
}
}
return $.param(urlOptions);
},
// Get array of keys from object
// see http://stackoverflow.com/questions/208016/how-to-list-the-properties-of-a-javascript-object/208020#208020
keysFromObject: function(obj){
var keys = [];
for (var key in obj){
keys.push(key);
}
return keys;
},
// Find an object in an array of objects by attributes.
// E.g. find object with {id: 'hi', name: 'there'} in an array of objects
findObjectInArray: function(array, objectAttr) {
var _this = this,
foundObject;
for (var i = 0, len = array.length; i < len; i++) {
var item = array[i];
// For each object in array, test to make sure all attributes in objectAttr match
if (_this.allMatch(item, objectAttr, function(item, key, value) { return item[key] == value; })) {
foundObject = item;
break;
}
}
return foundObject;
},
// Return true if supplied test function passes for ALL items in an array
allMatch: function(item, arrayOrObject, test) {
// start off with true result by default
var match = true,
isArray = $.isArray(arrayOrObject);
// Loop through all items in array
$.each(arrayOrObject, function(key, value) {
var result = isArray ? test(item, value) : test(item, key, value);
// If a single item tests false, go ahead and break the array by returning false
// and return false as result,
// otherwise, continue with next iteration in loop
// (if we make it through all iterations without overriding match with false,
// then we can return the true result we started with by default)
if (!result) { return match = false; }
});
return match;
},
// Return true if supplied test function passes for ANY items in an array
anyMatch: function(item, arrayOrObject, test) {
var match = false,
isArray = $.isArray(arrayOrObject);
$.each(arrayOrObject, function(key, value) {
var result = isArray ? test(item, value) : test(item, key, value);
if (result) {
// As soon as a match is found, set match to true, and return false to stop the `$.each` loop
match = true;
return false;
}
});
return match;
},
// Return true if two objects are equal
// (i.e. have the same attributes and attribute values)
objectsEqual: function(a, b) {
for (attr in a) {
if (a.hasOwnProperty(attr)) {
if (!b.hasOwnProperty(attr) || a[attr] !== b[attr]) {
return false;
}
}
}
for (attr in b) {
if (b.hasOwnProperty(attr) && !a.hasOwnProperty(attr)) {
return false;
}
}
return true;
},
// Taken from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/105074#105074
randomHash: function() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
},
// Adapted from http://stackoverflow.com/questions/377961/efficient-javascript-string-replacement/378001#378001
template: function(str, data) {
return str.replace(/{(\w*)}/g, function(match, key) {
return data.hasOwnProperty(key) ? data[key] : "";
});
}
};
//-----------------------------------------------------------------
// Build the dynatable plugin
//-----------------------------------------------------------------
// Object.create support test, and fallback for browsers without it
if ( typeof Object.create !== "function" ) {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
//-----------------------------------------------------------------
// Global dynatable plugin setting defaults
//-----------------------------------------------------------------
$.dynatableSetup = function(options) {
defaults = mergeSettings(options);
};
// Create dynatable plugin based on a defined object
$.dynatable = function( object ) {
$.fn['dynatable'] = function( options ) {
return this.each(function() {
if ( ! $.data( this, 'dynatable' ) ) {
$.data( this, 'dynatable', Object.create(object).init(this, options) );
}
});
};
};
$.dynatable(dt);
})(jQuery);
// Generated by CoffeeScript 1.4.0
/*
Easy pie chart is a jquery plugin to display simple animated pie charts for only one value
Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
Built on top of the jQuery library (http://jquery.com)
@source: http://github.com/rendro/easy-pie-chart/
@autor: Robert Fleischmann
@version: 1.0.1
Inspired by: http://dribbble.com/shots/631074-Simple-Pie-Charts-II?list=popular&offset=210
Thanks to Philip Thrasher for the jquery plugin boilerplate for coffee script
*/
(function() {
(function($) {
$.easyPieChart = function(el, options) {
var addScaleLine, animateLine, drawLine, easeInOutQuad, renderBackground, renderScale, renderTrack,
_this = this;
this.el = el;
this.$el = $(el);
this.$el.data("easyPieChart", this);
this.init = function() {
var percent;
_this.options = $.extend({}, $.easyPieChart.defaultOptions, options);
percent = parseInt(_this.$el.data('percent'), 10);
_this.percentage = 0;
_this.canvas = $("<canvas width='" + _this.options.size + "' height='" + _this.options.size + "'></canvas>").get(0);
_this.$el.append(_this.canvas);
if (typeof G_vmlCanvasManager !== "undefined" && G_vmlCanvasManager !== null) {
G_vmlCanvasManager.initElement(_this.canvas);
}
_this.ctx = _this.canvas.getContext('2d');
if (window.devicePixelRatio > 1.5) {
$(_this.canvas).css({
width: _this.options.size,
height: _this.options.size
});
_this.canvas.width *= 2;
_this.canvas.height *= 2;
_this.ctx.scale(2, 2);
}
_this.ctx.translate(_this.options.size / 2, _this.options.size / 2);
_this.$el.addClass('easyPieChart');
_this.$el.css({
width: _this.options.size,
height: _this.options.size,
lineHeight: "" + _this.options.size + "px"
});
_this.update(percent);
return _this;
};
this.update = function(percent) {
if (_this.options.animate === false) {
return drawLine(percent);
} else {
return animateLine(_this.percentage, percent);
}
};
renderScale = function() {
var i, _i, _results;
_this.ctx.fillStyle = _this.options.scaleColor;
_this.ctx.lineWidth = 1;
_results = [];
for (i = _i = 0; _i <= 24; i = ++_i) {
_results.push(addScaleLine(i));
}
return _results;
};
addScaleLine = function(i) {
var offset;
offset = i % 6 === 0 ? 0 : _this.options.size * 0.017;
_this.ctx.save();
_this.ctx.rotate(i * Math.PI / 12);
_this.ctx.fillRect(_this.options.size / 2 - offset, 0, -_this.options.size * 0.05 + offset, 1);
return _this.ctx.restore();
};
renderTrack = function() {
var offset;
offset = _this.options.size / 2 - _this.options.lineWidth / 2;
if (_this.options.scaleColor !== false) {
offset -= _this.options.size * 0.08;
}
_this.ctx.beginPath();
_this.ctx.arc(0, 0, offset, 0, Math.PI * 2, true);
_this.ctx.closePath();
_this.ctx.strokeStyle = _this.options.trackColor;
_this.ctx.lineWidth = _this.options.lineWidth;
return _this.ctx.stroke();
};
renderBackground = function() {
if (_this.options.scaleColor !== false) {
renderScale();
}
if (_this.options.trackColor !== false) {
return renderTrack();
}
};
drawLine = function(percent) {
var offset;
renderBackground();
_this.ctx.strokeStyle = $.isFunction(_this.options.barColor) ? _this.options.barColor(percent) : _this.options.barColor;
_this.ctx.lineCap = _this.options.lineCap;
_this.ctx.lineWidth = _this.options.lineWidth;
offset = _this.options.size / 2 - _this.options.lineWidth / 2;
if (_this.options.scaleColor !== false) {
offset -= _this.options.size * 0.08;
}
_this.ctx.save();
_this.ctx.rotate(-Math.PI / 2);
_this.ctx.beginPath();
_this.ctx.arc(0, 0, offset, 0, Math.PI * 2 * percent / 100, false);
_this.ctx.stroke();
return _this.ctx.restore();
};
animateLine = function(from, to) {
var currentStep, fps, steps;
fps = 30;
steps = fps * _this.options.animate / 1000;
currentStep = 0;
_this.options.onStart.call(_this);
_this.percentage = to;
if (_this.animation) {
clearInterval(_this.animation);
_this.animation = false;
}
return _this.animation = setInterval(function() {
_this.ctx.clearRect(-_this.options.size / 2, -_this.options.size / 2, _this.options.size, _this.options.size);
renderBackground.call(_this);
drawLine.call(_this, [easeInOutQuad(currentStep, from, to - from, steps)]);
currentStep++;
if ((currentStep / steps) > 1) {
clearInterval(_this.animation);
_this.animation = false;
return _this.options.onStop.call(_this);
}
}, 1000 / fps);
};
easeInOutQuad = function(t, b, c, d) {
var easeIn, easing;
easeIn = function(t) {
return Math.pow(t, 2);
};
easing = function(t) {
if (t < 1) {
return easeIn(t);
} else {
return 2 - easeIn((t / 2) * -2 + 2);
}
};
t /= d / 2;
return c / 2 * easing(t) + b;
};
return this.init();
};
$.easyPieChart.defaultOptions = {
barColor: '#ef1e25',
trackColor: '#f2f2f2',
scaleColor: '#dfe0e0',
lineCap: 'round',
size: 110,
lineWidth: 3,
animate: false,
onStart: $.noop,
onStop: $.noop
};
$.fn.easyPieChart = function(options) {
return $.each(this, function(i, el) {
var $el;
$el = $(el);
if (!$el.data('easyPieChart')) {
return $el.data('easyPieChart', new $.easyPieChart(el, options));
}
});
};
return void 0;
})(jQuery);
}).call(this);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment