2012-05-24 22:33:06 +00:00
|
|
|
|
|
|
|
// Chart design based on the recommendations of Stephen Few. Implementation
|
|
|
|
// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
|
|
|
|
// http://projects.instantcognition.com/protovis/bulletchart/
|
2012-08-17 20:23:26 +00:00
|
|
|
|
2012-05-26 08:55:32 +00:00
|
|
|
nv.models.bullet = function() {
|
2013-08-13 01:58:17 +00:00
|
|
|
"use strict";
|
2012-08-17 20:23:26 +00:00
|
|
|
//============================================================
|
|
|
|
// Public Variables with Default Settings
|
|
|
|
//------------------------------------------------------------
|
|
|
|
|
|
|
|
var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
|
|
|
, orient = 'left' // TODO top & bottom
|
|
|
|
, reverse = false
|
|
|
|
, ranges = function(d) { return d.ranges }
|
|
|
|
, markers = function(d) { return d.markers }
|
|
|
|
, measures = function(d) { return d.measures }
|
2013-07-15 16:33:26 +00:00
|
|
|
, rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
|
|
|
|
, markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
|
|
|
|
, measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
|
2012-09-18 21:17:45 +00:00
|
|
|
, forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
|
2012-08-17 20:23:26 +00:00
|
|
|
, width = 380
|
|
|
|
, height = 30
|
|
|
|
, tickFormat = null
|
2012-10-23 17:55:52 +00:00
|
|
|
, color = nv.utils.getColor(['#1f77b4'])
|
2012-08-17 20:23:26 +00:00
|
|
|
, dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
|
|
|
|
;
|
|
|
|
|
|
|
|
//============================================================
|
|
|
|
|
|
|
|
|
|
|
|
function chart(selection) {
|
|
|
|
selection.each(function(d, i) {
|
2012-06-30 07:17:21 +00:00
|
|
|
var availableWidth = width - margin.left - margin.right,
|
2012-08-17 20:23:26 +00:00
|
|
|
availableHeight = height - margin.top - margin.bottom,
|
2012-09-25 21:33:42 +00:00
|
|
|
container = d3.select(this);
|
2012-06-30 07:17:21 +00:00
|
|
|
|
2012-05-24 22:33:06 +00:00
|
|
|
var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
|
|
|
|
markerz = markers.call(this, d, i).slice().sort(d3.descending),
|
2013-07-15 16:33:26 +00:00
|
|
|
measurez = measures.call(this, d, i).slice().sort(d3.descending),
|
|
|
|
rangeLabelz = rangeLabels.call(this, d, i).slice(),
|
|
|
|
markerLabelz = markerLabels.call(this, d, i).slice(),
|
|
|
|
measureLabelz = measureLabels.call(this, d, i).slice();
|
2012-06-30 07:17:21 +00:00
|
|
|
|
|
|
|
|
2012-08-17 20:23:26 +00:00
|
|
|
//------------------------------------------------------------
|
|
|
|
// Setup Scales
|
2012-05-24 22:33:06 +00:00
|
|
|
|
|
|
|
// Compute the new x-scale.
|
|
|
|
var x1 = d3.scale.linear()
|
2012-12-03 22:14:41 +00:00
|
|
|
.domain( d3.extent(d3.merge([forceX, rangez])) )
|
2012-06-30 07:17:21 +00:00
|
|
|
.range(reverse ? [availableWidth, 0] : [0, availableWidth]);
|
2012-05-24 22:33:06 +00:00
|
|
|
|
|
|
|
// Retrieve the old x-scale, if this is an update.
|
|
|
|
var x0 = this.__chart__ || d3.scale.linear()
|
2012-09-21 15:08:26 +00:00
|
|
|
.domain([0, Infinity])
|
2012-05-24 22:33:06 +00:00
|
|
|
.range(x1.range());
|
|
|
|
|
|
|
|
// Stash the new scale.
|
|
|
|
this.__chart__ = x1;
|
|
|
|
|
2012-12-03 22:14:41 +00:00
|
|
|
|
|
|
|
var rangeMin = d3.min(rangez), //rangez[2]
|
|
|
|
rangeMax = d3.max(rangez), //rangez[0]
|
|
|
|
rangeAvg = rangez[1];
|
|
|
|
|
2012-08-17 20:23:26 +00:00
|
|
|
//------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------
|
|
|
|
// Setup containers and skeleton of chart
|
|
|
|
|
|
|
|
var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
|
|
|
|
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
|
|
|
|
var gEnter = wrapEnter.append('g');
|
|
|
|
var g = wrap.select('g');
|
|
|
|
|
2012-12-03 22:14:41 +00:00
|
|
|
gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
|
|
|
|
gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
|
|
|
|
gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
|
|
|
|
gEnter.append('rect').attr('class', 'nv-measure');
|
|
|
|
gEnter.append('path').attr('class', 'nv-markerTriangle');
|
|
|
|
|
2012-08-17 20:23:26 +00:00
|
|
|
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
|
|
|
|
|
|
|
//------------------------------------------------------------
|
|
|
|
|
|
|
|
|
2012-05-26 08:55:32 +00:00
|
|
|
|
|
|
|
var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
|
|
|
|
w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
|
2012-12-03 22:14:41 +00:00
|
|
|
var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
|
|
|
|
xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
|
|
|
|
|
|
|
|
|
|
|
|
g.select('rect.nv-rangeMax')
|
|
|
|
.attr('height', availableHeight)
|
|
|
|
.attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
|
|
|
|
.attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
|
|
|
|
.datum(rangeMax > 0 ? rangeMax : rangeMin)
|
|
|
|
/*
|
|
|
|
.attr('x', rangeMin < 0 ?
|
|
|
|
rangeMax > 0 ?
|
|
|
|
x1(rangeMin)
|
|
|
|
: x1(rangeMax)
|
|
|
|
: x1(0))
|
|
|
|
*/
|
|
|
|
|
|
|
|
g.select('rect.nv-rangeAvg')
|
|
|
|
.attr('height', availableHeight)
|
|
|
|
.attr('width', w1(rangeAvg))
|
|
|
|
.attr('x', xp1(rangeAvg))
|
|
|
|
.datum(rangeAvg)
|
|
|
|
/*
|
|
|
|
.attr('width', rangeMax <= 0 ?
|
|
|
|
x1(rangeMax) - x1(rangeAvg)
|
|
|
|
: x1(rangeAvg) - x1(rangeMin))
|
|
|
|
.attr('x', rangeMax <= 0 ?
|
|
|
|
x1(rangeAvg)
|
|
|
|
: x1(rangeMin))
|
|
|
|
*/
|
|
|
|
|
|
|
|
g.select('rect.nv-rangeMin')
|
|
|
|
.attr('height', availableHeight)
|
|
|
|
.attr('width', w1(rangeMax))
|
|
|
|
.attr('x', xp1(rangeMax))
|
|
|
|
.attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
|
|
|
|
.attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
|
|
|
|
.datum(rangeMax > 0 ? rangeMin : rangeMax)
|
|
|
|
/*
|
|
|
|
.attr('width', rangeMax <= 0 ?
|
|
|
|
x1(rangeAvg) - x1(rangeMin)
|
|
|
|
: x1(rangeMax) - x1(rangeAvg))
|
|
|
|
.attr('x', rangeMax <= 0 ?
|
|
|
|
x1(rangeMin)
|
|
|
|
: x1(rangeAvg))
|
|
|
|
*/
|
|
|
|
|
|
|
|
g.select('rect.nv-measure')
|
|
|
|
.style('fill', color)
|
|
|
|
.attr('height', availableHeight / 3)
|
|
|
|
.attr('y', availableHeight / 3)
|
|
|
|
.attr('width', measurez < 0 ?
|
|
|
|
x1(0) - x1(measurez[0])
|
|
|
|
: x1(measurez[0]) - x1(0))
|
|
|
|
.attr('x', xp1(measurez))
|
|
|
|
.on('mouseover', function() {
|
|
|
|
dispatch.elementMouseover({
|
|
|
|
value: measurez[0],
|
2013-07-16 15:26:30 +00:00
|
|
|
label: measureLabelz[0] || 'Current',
|
2012-12-03 22:14:41 +00:00
|
|
|
pos: [x1(measurez[0]), availableHeight/2]
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.on('mouseout', function() {
|
|
|
|
dispatch.elementMouseout({
|
|
|
|
value: measurez[0],
|
2013-07-16 15:26:30 +00:00
|
|
|
label: measureLabelz[0] || 'Current'
|
2012-12-03 22:14:41 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
var h3 = availableHeight / 6;
|
2012-12-13 21:28:43 +00:00
|
|
|
if (markerz[0]) {
|
|
|
|
g.selectAll('path.nv-markerTriangle')
|
|
|
|
.attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
|
|
|
|
.attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
|
|
|
|
.on('mouseover', function() {
|
|
|
|
dispatch.elementMouseover({
|
|
|
|
value: markerz[0],
|
2013-07-16 15:26:30 +00:00
|
|
|
label: markerLabelz[0] || 'Previous',
|
2012-12-13 21:28:43 +00:00
|
|
|
pos: [x1(markerz[0]), availableHeight/2]
|
|
|
|
})
|
2012-12-03 22:14:41 +00:00
|
|
|
})
|
2012-12-13 21:28:43 +00:00
|
|
|
.on('mouseout', function() {
|
|
|
|
dispatch.elementMouseout({
|
|
|
|
value: markerz[0],
|
2013-07-16 15:26:30 +00:00
|
|
|
label: markerLabelz[0] || 'Previous'
|
2012-12-13 21:28:43 +00:00
|
|
|
})
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
g.selectAll('path.nv-markerTriangle').remove();
|
|
|
|
}
|
2012-05-26 08:55:32 +00:00
|
|
|
|
|
|
|
|
2012-12-03 22:14:41 +00:00
|
|
|
wrap.selectAll('.nv-range')
|
|
|
|
.on('mouseover', function(d,i) {
|
2013-07-16 15:26:30 +00:00
|
|
|
var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
|
2012-12-03 22:14:41 +00:00
|
|
|
|
|
|
|
dispatch.elementMouseover({
|
|
|
|
value: d,
|
|
|
|
label: label,
|
|
|
|
pos: [x1(d), availableHeight/2]
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.on('mouseout', function(d,i) {
|
2013-07-16 15:26:30 +00:00
|
|
|
var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
|
2012-12-03 22:14:41 +00:00
|
|
|
|
|
|
|
dispatch.elementMouseout({
|
|
|
|
value: d,
|
|
|
|
label: label
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
/* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY
|
2012-05-24 22:33:06 +00:00
|
|
|
// Update the range rects.
|
2012-07-26 23:42:42 +00:00
|
|
|
var range = g.selectAll('rect.nv-range')
|
2012-05-24 22:33:06 +00:00
|
|
|
.data(rangez);
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
range.enter().append('rect')
|
2012-07-26 23:42:42 +00:00
|
|
|
.attr('class', function(d, i) { return 'nv-range nv-s' + i; })
|
2012-06-30 07:17:21 +00:00
|
|
|
.attr('width', w0)
|
|
|
|
.attr('height', availableHeight)
|
|
|
|
.attr('x', reverse ? x0 : 0)
|
2012-05-26 08:55:32 +00:00
|
|
|
.on('mouseover', function(d,i) {
|
|
|
|
dispatch.elementMouseover({
|
|
|
|
value: d,
|
|
|
|
label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable
|
2012-09-25 21:33:42 +00:00
|
|
|
pos: [x1(d), availableHeight/2]
|
2012-05-26 08:55:32 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
.on('mouseout', function(d,i) {
|
|
|
|
dispatch.elementMouseout({
|
|
|
|
value: d,
|
2012-08-16 19:42:58 +00:00
|
|
|
label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable
|
2012-05-26 08:55:32 +00:00
|
|
|
})
|
|
|
|
})
|
2012-05-24 22:33:06 +00:00
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
d3.transition(range)
|
|
|
|
.attr('x', reverse ? x1 : 0)
|
|
|
|
.attr('width', w1)
|
|
|
|
.attr('height', availableHeight);
|
2012-05-24 22:33:06 +00:00
|
|
|
|
2012-05-26 08:55:32 +00:00
|
|
|
|
2012-05-24 22:33:06 +00:00
|
|
|
// Update the measure rects.
|
2012-07-26 23:42:42 +00:00
|
|
|
var measure = g.selectAll('rect.nv-measure')
|
2012-05-24 22:33:06 +00:00
|
|
|
.data(measurez);
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
measure.enter().append('rect')
|
2012-07-26 23:42:42 +00:00
|
|
|
.attr('class', function(d, i) { return 'nv-measure nv-s' + i; })
|
2012-10-23 17:55:52 +00:00
|
|
|
.style('fill', function(d,i) { return color(d,i ) })
|
2012-06-30 07:17:21 +00:00
|
|
|
.attr('width', w0)
|
|
|
|
.attr('height', availableHeight / 3)
|
|
|
|
.attr('x', reverse ? x0 : 0)
|
|
|
|
.attr('y', availableHeight / 3)
|
2012-05-26 08:55:32 +00:00
|
|
|
.on('mouseover', function(d) {
|
|
|
|
dispatch.elementMouseover({
|
|
|
|
value: d,
|
|
|
|
label: 'Current', //TODO: make these labels a variable
|
2012-09-25 21:33:42 +00:00
|
|
|
pos: [x1(d), availableHeight/2]
|
2012-05-26 08:55:32 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
.on('mouseout', function(d) {
|
|
|
|
dispatch.elementMouseout({
|
|
|
|
value: d,
|
|
|
|
label: 'Current' //TODO: make these labels a variable
|
|
|
|
})
|
|
|
|
})
|
2012-05-24 22:33:06 +00:00
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
d3.transition(measure)
|
|
|
|
.attr('width', w1)
|
|
|
|
.attr('height', availableHeight / 3)
|
|
|
|
.attr('x', reverse ? x1 : 0)
|
|
|
|
.attr('y', availableHeight / 3);
|
2012-05-24 22:33:06 +00:00
|
|
|
|
2012-05-26 08:55:32 +00:00
|
|
|
|
|
|
|
|
2012-05-24 22:33:06 +00:00
|
|
|
// Update the marker lines.
|
2012-07-26 23:42:42 +00:00
|
|
|
var marker = g.selectAll('path.nv-markerTriangle')
|
2012-05-24 22:33:06 +00:00
|
|
|
.data(markerz);
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
var h3 = availableHeight / 6;
|
|
|
|
marker.enter().append('path')
|
2012-07-26 23:42:42 +00:00
|
|
|
.attr('class', 'nv-markerTriangle')
|
2012-06-30 07:17:21 +00:00
|
|
|
.attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' })
|
2012-05-26 08:55:32 +00:00
|
|
|
.attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
|
2012-07-26 23:42:42 +00:00
|
|
|
.on('mouseover', function(d,i) {
|
2012-05-26 08:55:32 +00:00
|
|
|
dispatch.elementMouseover({
|
|
|
|
value: d,
|
|
|
|
label: 'Previous',
|
2012-09-25 21:33:42 +00:00
|
|
|
pos: [x1(d), availableHeight/2]
|
2012-05-26 08:55:32 +00:00
|
|
|
})
|
|
|
|
})
|
2012-07-26 23:42:42 +00:00
|
|
|
.on('mouseout', function(d,i) {
|
2012-05-26 08:55:32 +00:00
|
|
|
dispatch.elementMouseout({
|
|
|
|
value: d,
|
|
|
|
label: 'Previous'
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
d3.transition(marker)
|
2012-11-29 05:59:02 +00:00
|
|
|
.attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' });
|
2012-05-26 08:55:32 +00:00
|
|
|
|
|
|
|
marker.exit().remove();
|
2012-12-03 22:14:41 +00:00
|
|
|
*/
|
2012-05-26 08:55:32 +00:00
|
|
|
|
2012-05-24 22:33:06 +00:00
|
|
|
});
|
2012-08-17 20:23:26 +00:00
|
|
|
|
2012-12-03 22:14:41 +00:00
|
|
|
// d3.timer.flush(); // Not needed?
|
2012-08-17 20:23:26 +00:00
|
|
|
|
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
}
|
|
|
|
|
2012-05-26 08:55:32 +00:00
|
|
|
|
2012-08-17 20:23:26 +00:00
|
|
|
//============================================================
|
|
|
|
// Expose Public Variables
|
|
|
|
//------------------------------------------------------------
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
chart.dispatch = dispatch;
|
2012-05-26 08:55:32 +00:00
|
|
|
|
2013-08-25 02:27:13 +00:00
|
|
|
chart.options = nv.utils.optionsFunc.bind(chart);
|
|
|
|
|
2012-05-24 22:33:06 +00:00
|
|
|
// left, right, top, bottom
|
2012-06-30 07:17:21 +00:00
|
|
|
chart.orient = function(_) {
|
2012-05-24 22:33:06 +00:00
|
|
|
if (!arguments.length) return orient;
|
2012-06-30 07:17:21 +00:00
|
|
|
orient = _;
|
|
|
|
reverse = orient == 'right' || orient == 'bottom';
|
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// ranges (bad, satisfactory, good)
|
2012-06-30 07:17:21 +00:00
|
|
|
chart.ranges = function(_) {
|
2012-05-24 22:33:06 +00:00
|
|
|
if (!arguments.length) return ranges;
|
2012-06-30 07:17:21 +00:00
|
|
|
ranges = _;
|
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// markers (previous, goal)
|
2012-06-30 07:17:21 +00:00
|
|
|
chart.markers = function(_) {
|
2012-05-24 22:33:06 +00:00
|
|
|
if (!arguments.length) return markers;
|
2012-06-30 07:17:21 +00:00
|
|
|
markers = _;
|
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// measures (actual, forecast)
|
2012-06-30 07:17:21 +00:00
|
|
|
chart.measures = function(_) {
|
2012-05-24 22:33:06 +00:00
|
|
|
if (!arguments.length) return measures;
|
2012-06-30 07:17:21 +00:00
|
|
|
measures = _;
|
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
};
|
|
|
|
|
2012-09-18 21:17:45 +00:00
|
|
|
chart.forceX = function(_) {
|
|
|
|
if (!arguments.length) return forceX;
|
|
|
|
forceX = _;
|
|
|
|
return chart;
|
|
|
|
};
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
chart.width = function(_) {
|
2012-05-24 22:33:06 +00:00
|
|
|
if (!arguments.length) return width;
|
2012-06-30 07:17:21 +00:00
|
|
|
width = _;
|
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
};
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
chart.height = function(_) {
|
2012-05-24 22:33:06 +00:00
|
|
|
if (!arguments.length) return height;
|
2012-06-30 07:17:21 +00:00
|
|
|
height = _;
|
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
};
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
chart.margin = function(_) {
|
|
|
|
if (!arguments.length) return margin;
|
2012-08-27 17:55:42 +00:00
|
|
|
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
|
|
|
|
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
|
|
|
|
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
|
|
|
|
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
|
2012-06-30 07:17:21 +00:00
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
};
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
chart.tickFormat = function(_) {
|
|
|
|
if (!arguments.length) return tickFormat;
|
|
|
|
tickFormat = _;
|
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
};
|
|
|
|
|
2012-10-23 17:55:52 +00:00
|
|
|
chart.color = function(_) {
|
|
|
|
if (!arguments.length) return color;
|
|
|
|
color = nv.utils.getColor(_);
|
|
|
|
return chart;
|
|
|
|
};
|
|
|
|
|
2012-08-17 20:23:26 +00:00
|
|
|
//============================================================
|
|
|
|
|
|
|
|
|
2012-06-30 07:17:21 +00:00
|
|
|
return chart;
|
2012-05-24 22:33:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|