oxjs/source/Ox.UI/js/List/Chart.js

277 lines
9.8 KiB
JavaScript

'use strict';
/*@
Ox.Chart <f> Bar Chart
options <o> Options
color <[n]|[[n]]|[128, 128, 128]> Bar color
data <o> {k: v, ...} or {k: {k: v, ...}, ...}
formatKey <f|null> Format function for keys
keyAlign <s|'right'> Alignment of keys
keyWidth <n|128> Width of keys
rows <n|1> undocumented
sort <o|{key: 'value', operator: '-'}> Sort
title <s|''> Chart title
width <n|512> Chart width
self <o> shared private variable
([options[, self]]) -> <o:Ox.Element> Chart object
@*/
Ox.Chart = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
color: [128, 128, 128],
data: {},
formatKey: null,
keyAlign: 'right',
keyWidth: 128,
rows: 1,
sort: {key: 'value', operator: '-'},
sortKey: null,
title: '',
width: 512
})
.options(options || {})
.update({
width: function() {
self.$chart.empty();
renderChart();
}
})
.addClass('OxChart')
.css({
width: self.options.width + 'px',
height: 16 + Ox.len(self.options.data) * 16 + 'px',
overflowY: 'hidden'
});
self.valueWidth = self.options.width - self.options.keyWidth;
self.keys = Object.keys(self.options.data);
if (Ox.isObject(self.options.data[self.keys[0]])) {
if (Ox.isUndefined(options.color)) {
self.options.color = [
[192, 64, 64], [ 64, 192, 64], [ 64, 64, 192],
[192, 192, 64], [ 64, 192, 192], [192, 64, 192],
[192, 128, 64], [ 64, 192, 128], [128, 64, 192],
[192, 64, 128], [128, 192, 64], [ 64, 128, 192]
];
}
self.subData = {};
}
// fixme: a lot of the sorting is unneeded, since it happens in the list
self.sort = {};
self.totals = {};
Ox.forEach(self.options.data, function(value, key) {
self.totals[key] = self.subData ? Ox.sum(value) : value;
if (self.subData) {
Object.keys(value).forEach(function(subKey) {
self.subData[subKey] = (self.subData[subKey] || 0) + value[subKey];
});
}
self.sort[key] = key.replace(/(\d+)/g, function(number) {
return Ox.pad(parseInt(number, 10), 16);
});
});
self.max = Ox.max(self.totals);
self.sum = Ox.sum(self.totals);
if (self.subData) {
Ox.forEach(self.subData, function(subValue, subKey) {
self.sort[subKey] = subKey.replace(/(\d+)/g, function(number) {
return Ox.pad(parseInt(number, 10), 16);
});
});
self.subKeys = Object.keys(self.subData).sort(function(a, b) {
var aValue = self.subData[a],
bValue = self.subData[b];
return a === '' ? 1
: b === '' ? -1
//: aValue < bValue ? 1
//: aValue > bValue ? -1
: self.sort[a] < self.sort[b] ? -1
: self.sort[a] > self.sort[b] ? 1
: 0;
});
}
self.items = self.keys.map(function(key) {
return {key: key, value: self.options.data[key]};
})
.sort(function(a, b) {
var key = self.options.sort.key,
aValue = key == 'key' ? self.sort[a.key]
: self.subData ? self.totals[a.key] : a.value,
bValue = key == 'key' ? self.sort[b.key]
: self.subData ? self.totals[b.key] : b.value;
return aValue < bValue ? (self.options.sort.operator == '+' ? -1 : 1)
: aValue > bValue ? (self.options.sort.operator == '+' ? 1 : -1)
: key == 'value' && self.sort[a.key] < self.sort[b.key] ? -1
: key == 'value' && self.sort[a.key] > self.sort[b.key] ? 1
: 0;
});
if (self.options.rows == 2) {
self.row = 0;
}
self.$title = Ox.Bar({size: 16})
.append(
$('<div>')
.css({margin: '1px 0 0 4px'})
.html(self.options.title)
)
.appendTo(that);
self.$chart = $('<div>')
.css({position: 'absolute', top: '16px'})
.append(renderChart())
.appendTo(that);
function getColumns() {
return [
{
align: self.options.keyAlign,
format: self.options.formatKey,
id: 'key',
width: self.options.keyWidth,
visible: true
},
{
format: renderValue,
id: 'value',
width: self.valueWidth,
visible: true
}
];
}
function getWidths(values) {
var max, maxKeys,
total = Ox.sum(values),
totalWidth = Math.round(total / self.max * self.valueWidth),
widths = {};
Ox.forEach(values, function(value, key) {
widths[key] = Math.round(value / total * totalWidth);
});
while (Ox.sum(widths) != totalWidth) {
max = Ox.max(widths);
maxKeys = Object.keys(widths).filter(function(key) {
return widths[key] == max;
});
widths[maxKeys[0]] += Ox.sum(widths) < totalWidth ? 1 : -1;
}
return widths;
}
function renderChart() {
return Ox.TableList({
columns: getColumns(),
items: self.items,
max: 0,
min: 0,
pageLength: self.items.length,
sort: [self.options.sort],
width: self.options.width,
unique: 'key'
})
.css({
left: 0,
top: 0,
width: self.options.width + 'px',
height: self.items.length * 16 + 'px'
});
}
function renderValue(value, data) {
var $bars = [],
$element,
colors = [], len, widths;
if (!self.subData) {
$element = $bars[0] = Ox.Element({
element: '<div>',
tooltip: Ox.formatNumber(value)
+ ' (' + Ox.formatPercent(value * self.options.rows, self.sum, 2) + ')'
})
.css({
width: Math.round(value / self.max * self.valueWidth) + 'px',
height: '14px',
borderRadius: '4px',
marginLeft: '-4px'
});
colors[0] = Ox.isFunction(self.options.color)
? self.options.color(data.key) : self.options.color;
} else {
$element = $('<div>')
.css({
width: Math.round(self.totals[data.key] / self.max * self.valueWidth) + 'px',
height: '14px',
marginLeft: '-4px'
});
len = Ox.len(value);
widths = getWidths(value);
self.subKeys.forEach(function(subKey, subKeyIndex) {
var i = $bars.length,
subValue = value[subKey];
if (subValue) {
$bars[i] = Ox.Element({
element: '<div>',
/*
tooltip: Ox.formatNumber(self.totals[data.key])
+ ' (' + Ox.formatPercent(self.totals[data.key] * self.options.rows, self.sum, 2) + ')'
+ '<br>' + subKey + ': ' + Ox.formatNumber(subValue)
+ ' (' + Ox.formatPercent(subValue, self.totals[data.key], 2) + ')'
*/
tooltip: Ox.formatNumber(self.totals[data.key])
+ ' (' + Ox.formatPercent(self.totals[data.key] * self.options.rows, self.sum, 2) + ')'
})
.css({
float: 'left',
width: widths[subKey] + 'px',
height: '14px',
borderTopLeftRadius: i == 0 ? '4px' : 0,
borderBottomLeftRadius: i == 0 ? '4px' : 0,
borderTopRightRadius: i == len - 1 ? '4px' : 0,
borderBottomRightRadius: i == len - 1 ? '4px' : 0
})
.appendTo($element);
colors[i] = subKey == '' ? [128, 128, 128]
: Ox.isArray(self.options.color)
? self.options.color[subKeyIndex % self.options.color.length]
: Ox.isObject(self.options.color)
? self.options.color[subKey]
: self.options.color(subKey);
}
});
}
$bars.forEach(function($bar, i) {
/*
if (self.options.rows == 2) {
colors[i] = colors[i].map(function(v) {
return v + (self.row % 2 == 0 ? 16 : -16);
});
}
*/
['moz', 'o', 'webkit'].forEach(function(browser) {
$bar.css({
backgroundImage: '-' + browser
+ '-linear-gradient(top, rgb(' + colors[i].map(function(v) {
return Ox.limit(v + 16, 0, 255);
}).join(', ') + '), rgb(' + colors[i].map(function(v) {
return Ox.limit(v - 16, 0, 255);
}) + '))'
});
});
});
if (self.options.rows == 2) {
self.row++;
}
return $element;
}
return that;
};