PlotKit.Layout.prototype.evaluate = function() {
this._evaluateLimits();
this._evaluateScales();
- if (this.style == "bar") {
- if (this.options.barOrientation == "horizontal") {
- this._evaluateHorizBarCharts();
- }
- else {
- this._evaluateBarCharts();
- }
- this._evaluateBarTicks();
- }
- else if (this.style == "line") {
+ if (this.style == "line") {
this._evaluateLineCharts();
this._evaluateLineTicks();
}
- else if (this.style == "pie") {
- this._evaluatePieCharts();
- this._evaluatePieTicks();
- }
};
-// Given the fractional x, y positions, report the corresponding
-// x, y values.
-PlotKit.Layout.prototype.hitTest = function(x, y) {
- // TODO: make this more efficient with better datastructures
- // for this.bars, this.points and this.slices
-
- var f = MochiKit.Format.twoDigitFloat;
-
- if ((this.style == "bar") && this.bars && (this.bars.length > 0)) {
- for (var i = 0; i < this.bars.length; i++) {
- var bar = this.bars[i];
- if ((x >= bar.x) && (x <= bar.x + bar.w)
- && (y >= bar.y) && (y - bar.y <= bar.h))
- return bar;
- }
- }
-
- else if (this.style == "line") {
- if (this.hitTestCache.x2maxy == null) {
- this._regenerateHitTestCache();
- }
-
- // 1. find the xvalues that equal or closest to the give x
- var xval = x / this.xscale;
- var xvalues = this.hitTestCache.xvalues;
- var xbefore = null;
- var xafter = null;
-
- for (var i = 1; i < xvalues.length; i++) {
- if (xvalues[i] > xval) {
- xbefore = xvalues[i-1];
- xafter = xvalues[i];
- break;
- }
- }
-
- if ((xbefore != null)) {
- var ybefore = this.hitTestCache.x2maxy[xbefore];
- var yafter = this.hitTestCache.x2maxy[xafter];
- var yval = (1.0 - y)/this.yscale;
-
- // interpolate whether we will fall inside or outside
- var gradient = (yafter - ybefore) / (xafter - xbefore);
- var projmaxy = ybefore + gradient * (xval - xbefore);
- if (projmaxy >= yval) {
- // inside the highest curve (roughly)
- var obj = {xval: xval, yval: yval,
- xafter: xafter, yafter: yafter,
- xbefore: xbefore, ybefore: ybefore,
- yprojected: projmaxy
- };
- return obj;
- }
- }
- }
-
- else if (this.style == "pie") {
- var dist = Math.sqrt((y-0.5)*(y-0.5) + (x-0.5)*(x-0.5));
- if (dist > this.options.pieRadius)
- return null;
-
- // TODO: actually doesn't work if we don't know how the Canvas
- // lays it out, need to fix!
- var angle = Math.atan2(y - 0.5, x - 0.5) - Math.PI/2;
- for (var i = 0; i < this.slices.length; i++) {
- var slice = this.slices[i];
- if (slice.startAngle < angle && slice.endAngle >= angle)
- return slice;
- }
- }
-
- return null;
-};
-
-// Reports valid position rectangle for X value (only valid for bar charts)
-PlotKit.Layout.prototype.rectForX = function(x) {
- return null;
-};
-
-// Reports valid angles through which X value encloses (only valid for pie charts)
-PlotKit.Layout.prototype.angleRangeForX = function(x) {
- return null;
-};
// --------------------------------------------------------------------
// START Internal Functions
return uniq(xvalues);
};
-// Create the bars
-PlotKit.Layout.prototype._evaluateBarCharts = function() {
- var items = PlotKit.Base.items;
-
- var setCount = items(this.datasets).length;
-
- // work out how far separated values are
- var xdelta = 10000000;
- var xvalues = this._uniqueXValues();
- for (var i = 1; i < xvalues.length; i++) {
- xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta);
- }
-
- var barWidth = 0;
- var barWidthForSet = 0;
- var barMargin = 0;
- if (xvalues.length == 1) {
- // note we have to do something smarter if we only plot one value
- xdelta = 1.0;
- this.xscale = 1.0;
- this.minxval = xvalues[0];
- barWidth = 1.0 * this.options.barWidthFillFraction;
- barWidthForSet = barWidth/setCount;
- barMargin = (1.0 - this.options.barWidthFillFraction)/2;
- }
- else {
- // readjust xscale to fix with bar charts
- if (this.xrange == 1) {
- this.xscale = 0.5;
- }
- else if (this.xrange == 2) {
- this.xscale = 1/3.0;
- }
- else {
- this.xscale = (1.0 - xdelta/this.xrange)/this.xrange;
- }
- barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
- barWidthForSet = barWidth / setCount;
- barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
- }
-
- this.minxdelta = xdelta; // need this for tick positions
-
- // add all the rects
- this.bars = new Array();
- var i = 0;
- for (var setName in this.datasets) {
- var dataset = this.datasets[setName];
- if (PlotKit.Base.isFuncLike(dataset)) continue;
- for (var j = 0; j < dataset.length; j++) {
- var item = dataset[j];
- var rect = {
- x: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
- y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
- w: barWidthForSet,
- h: ((parseFloat(item[1]) - this.minyval) * this.yscale),
- xval: parseFloat(item[0]),
- yval: parseFloat(item[1]),
- name: setName
- };
- if ((rect.x >= 0.0) && (rect.x <= 1.0) &&
- (rect.y >= 0.0) && (rect.y <= 1.0)) {
- this.bars.push(rect);
- }
- }
- i++;
- }
-};
-
-// Create the horizontal bars
-PlotKit.Layout.prototype._evaluateHorizBarCharts = function() {
- var items = PlotKit.Base.items;
-
- var setCount = items(this.datasets).length;
-
- // work out how far separated values are
- var xdelta = 10000000;
- var xvalues = this._uniqueXValues();
- for (var i = 1; i < xvalues.length; i++) {
- xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta);
- }
-
- var barWidth = 0;
- var barWidthForSet = 0;
- var barMargin = 0;
-
- // work out how far each far each bar is separated
- if (xvalues.length == 1) {
- // do something smarter if we only plot one value
- xdelta = 1.0;
- this.xscale = 1.0;
- this.minxval = xvalues[0];
- barWidth = 1.0 * this.options.barWidthFillFraction;
- barWidthForSet = barWidth/setCount;
- barMargin = (1.0 - this.options.barWidthFillFraction)/2;
- }
- else {
- // readjust yscale to fix with bar charts
- this.xscale = (1.0 - xdelta/this.xrange)/this.xrange;
- barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
- barWidthForSet = barWidth / setCount;
- barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
- }
-
- this.minxdelta = xdelta; // need this for tick positions
-
- // add all the rects
- this.bars = new Array();
- var i = 0;
- for (var setName in this.datasets) {
- var dataset = this.datasets[setName];
- if (PlotKit.Base.isFuncLike(dataset)) continue;
- for (var j = 0; j < dataset.length; j++) {
- var item = dataset[j];
- var rect = {
- y: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
- x: 0.0,
- h: barWidthForSet,
- w: ((parseFloat(item[1]) - this.minyval) * this.yscale),
- xval: parseFloat(item[0]),
- yval: parseFloat(item[1]),
- name: setName
- };
-
- // limit the x, y values so they do not overdraw
- if (rect.y <= 0.0) {
- rect.y = 0.0;
- }
- if (rect.y >= 1.0) {
- rect.y = 1.0;
- }
- if ((rect.x >= 0.0) && (rect.x <= 1.0)) {
- this.bars.push(rect);
- }
- }
- i++;
- }
-};
-
// Create the line charts
PlotKit.Layout.prototype._evaluateLineCharts = function() {
}
};
-// Create the pie charts
-PlotKit.Layout.prototype._evaluatePieCharts = function() {
- var items = PlotKit.Base.items;
- var sum = MochiKit.Iter.sum;
- var getter = MochiKit.Base.itemgetter;
-
- var setCount = items(this.datasets).length;
-
- // we plot the y values of the first dataset
- var dataset = items(this.datasets)[0][1];
- var total = sum(map(getter(1), dataset));
-
- this.slices = new Array();
- var currentAngle = 0.0;
- for (var i = 0; i < dataset.length; i++) {
- var fraction = dataset[i][1] / total;
- var startAngle = currentAngle * Math.PI * 2;
- var endAngle = (currentAngle + fraction) * Math.PI * 2;
-
- var slice = {fraction: fraction,
- xval: dataset[i][0],
- yval: dataset[i][1],
- startAngle: startAngle,
- endAngle: endAngle
- };
- if (dataset[i][1] != 0) {
- this.slices.push(slice);
- }
- currentAngle += fraction;
- }
-};
PlotKit.Layout.prototype._evaluateLineTicksForXAxis = function() {
var isNil = MochiKit.Base.isUndefinedOrNull;
this._evaluateLineTicksForYAxis();
};
-PlotKit.Layout.prototype._evaluateBarTicks = function() {
- this._evaluateLineTicks();
- var centerInBar = function(tick) {
- return [tick[0] + (this.minxdelta * this.xscale)/2, tick[1]];
- };
- this.xticks = MochiKit.Base.map(bind(centerInBar, this), this.xticks);
-
- if (this.options.barOrientation == "horizontal") {
- // swap scales
- var tempticks = this.xticks;
- this.xticks = this.yticks;
- this.yticks = tempticks;
-
- // we need to invert the "yaxis" (which is now the xaxis when drawn)
- var invert = function(tick) {
- return [1.0 - tick[0], tick[1]];
- }
- this.xticks = MochiKit.Base.map(invert, this.xticks);
- }
-};
-
-PlotKit.Layout.prototype._evaluatePieTicks = function() {
- var isNil = MochiKit.Base.isUndefinedOrNull;
- var formatter = MochiKit.Format.numberFormatter("#%");
-
- this.xticks = new Array();
- if (this.options.xTicks) {
- // make a lookup dict for x->slice values
- var lookup = new Array();
- for (var i = 0; i < this.slices.length; i++) {
- lookup[this.slices[i].xval] = this.slices[i];
- }
-
- for (var i =0; i < this.options.xTicks.length; i++) {
- var tick = this.options.xTicks[i];
- var slice = lookup[tick.v];
- var label = tick.label;
- if (slice) {
- if (isNil(label))
- label = tick.v.toString();
- label += " (" + formatter(slice.fraction) + ")";
- this.xticks.push([tick.v, label]);
- }
- }
- }
- else {
- // we make our own labels from all the slices
- for (var i =0; i < this.slices.length; i++) {
- var slice = this.slices[i];
- var label = slice.xval + " (" + formatter(slice.fraction) + ")";
- this.xticks.push([slice.xval, label]);
- }
- }
-};
-
-PlotKit.Layout.prototype._regenerateHitTestCache = function() {
- this.hitTestCache.xvalues = this._uniqueXValues();
- this.hitTestCache.xlookup = new Array();
- this.hitTestCache.x2maxy = new Array();
-
- var listMax = MochiKit.Base.listMax;
- var itemgetter = MochiKit.Base.itemgetter;
- var map = MochiKit.Base.map;
-
- // generate a lookup table for x values to y values
- var setNames = keys(this.datasets);
- for (var i = 0; i < setNames.length; i++) {
- var dataset = this.datasets[setNames[i]];
- for (var j = 0; j < dataset.length; j++) {
- var xval = dataset[j][0];
- var yval = dataset[j][1];
- if (this.hitTestCache.xlookup[xval])
- this.hitTestCache.xlookup[xval].push([yval, setNames[i]]);
- else
- this.hitTestCache.xlookup[xval] = [[yval, setNames[i]]];
- }
- }
-
- for (var x in this.hitTestCache.xlookup) {
- var yvals = this.hitTestCache.xlookup[x];
- this.hitTestCache.x2maxy[x] = listMax(map(itemgetter(0), yvals));
- }
-
-
-};
// --------------------------------------------------------------------
// END Internal Functions