📄 layout.js
字号:
/* PlotKit Layout ============== Handles laying out data on to a virtual canvas square canvas between 0.0 and 1.0. If you want to add new chart/plot types such as point plots, you need to add them here. Copyright --------- Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net> For use under the BSD license. <http://www.liquidx.net/plotkit> */try { if (typeof(PlotKit.Base) == 'undefined') { throw "" }} catch (e) { throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base"}// --------------------------------------------------------------------// Start of Layout definition// --------------------------------------------------------------------if (typeof(PlotKit.Layout) == 'undefined') { PlotKit.Layout = {};}PlotKit.Layout.NAME = "PlotKit.Layout";PlotKit.Layout.VERSION = PlotKit.VERSION;PlotKit.Layout.__repr__ = function() { return "[" + this.NAME + " " + this.VERSION + "]";};PlotKit.Layout.toString = function() { return this.__repr__();}PlotKit.Layout.valid_styles = ["bar", "line", "pie", "point"];// --------------------------------------------------------------------// Start of Layout definition// --------------------------------------------------------------------PlotKit.Layout = function(style, options) { this.options = { "barWidthFillFraction": 0.75, "barOrientation": "vertical", "xOriginIsZero": true, "yOriginIsZero": true, "xAxis": null, // [xmin, xmax] "yAxis": null, // [ymin, ymax] "xTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.) "yTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.) "xNumberOfTicks": 10, "yNumberOfTicks": 5, "xTickPrecision": 1, "yTickPrecision": 1, "pieRadius": 0.4 }; // valid external options : TODO: input verification this.style = style; MochiKit.Base.update(this.options, options ? options : {}); // externally visible states // overriden if xAxis and yAxis are set in options if (!MochiKit.Base.isUndefinedOrNull(this.options.xAxis)) { this.minxval = this.options.xAxis[0]; this.maxxval = this.options.xAxis[1]; this.xscale = this.maxxval - this.minxval; } else { this.minxval = 0; this.maxxval = null; this.xscale = null; // val -> pos factor (eg, xval * xscale = xpos) } if (!MochiKit.Base.isUndefinedOrNull(this.options.yAxis)) { this.minyval = this.options.yAxis[0]; this.maxyval = this.options.yAxis[1]; this.yscale = this.maxyval - this.minyval; } else { this.minyval = 0; this.maxyval = null; this.yscale = null; } this.bars = new Array(); // array of bars to plot for bar charts this.points = new Array(); // array of points to plot for line plots this.slices = new Array(); // array of slices to draw for pie charts this.xticks = new Array(); this.yticks = new Array(); // internal states this.datasets = new Array(); this.minxdelta = 0; this.xrange = 1; this.yrange = 1; this.hitTestCache = {x2maxy: null}; };// --------------------------------------------------------------------// Dataset Manipulation// --------------------------------------------------------------------PlotKit.Layout.prototype.addDataset = function(setname, set_xy) { this.datasets[setname] = set_xy;};PlotKit.Layout.prototype.removeDataset = function(setname, set_xy) { delete this.datasets[setname];};PlotKit.Layout.prototype.addDatasetFromTable = function(name, tableElement, xcol, ycol, lcol) { var isNil = MochiKit.Base.isUndefinedOrNull; var scrapeText = MochiKit.DOM.scrapeText; var strip = MochiKit.Format.strip; if (isNil(xcol)) xcol = 0; if (isNil(ycol)) ycol = 1; if (isNil(lcol)) lcol = -1; var rows = tableElement.tBodies[0].rows; var data = new Array(); var labels = new Array(); if (!isNil(rows)) { for (var i = 0; i < rows.length; i++) { data.push([parseFloat(strip(scrapeText(rows[i].cells[xcol]))), parseFloat(strip(scrapeText(rows[i].cells[ycol])))]); if (lcol >= 0){ labels.push({v: parseFloat(strip(scrapeText(rows[i].cells[xcol]))), label: strip(scrapeText(rows[i].cells[lcol]))}); } } this.addDataset(name, data); if (lcol >= 0) { this.options.xTicks = labels; } return true; } return false;};// --------------------------------------------------------------------// Evaluates the layout for the current data and style.// --------------------------------------------------------------------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") { 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// --------------------------------------------------------------------PlotKit.Layout.prototype._evaluateLimits = function() { // take all values from all datasets and find max and min var map = PlotKit.Base.map; var items = PlotKit.Base.items; var itemgetter = MochiKit.Base.itemgetter; var collapse = PlotKit.Base.collapse; var listMin = MochiKit.Base.listMin; var listMax = MochiKit.Base.listMax; var isNil = MochiKit.Base.isUndefinedOrNull; var all = collapse(map(itemgetter(1), items(this.datasets))); if (isNil(this.options.xAxis)) { if (this.options.xOriginIsZero) this.minxval = 0; else this.minxval = listMin(map(parseFloat, map(itemgetter(0), all))); this.maxxval = listMax(map(parseFloat, map(itemgetter(0), all))); } else { this.minxval = this.options.xAxis[0]; this.maxxval = this.options.xAxis[1]; this.xscale = this.maxval - this.minxval; } if (isNil(this.options.yAxis)) { if (this.options.yOriginIsZero) this.minyval = 0; else this.minyval = listMin(map(parseFloat, map(itemgetter(1), all))); this.maxyval = listMax(map(parseFloat, map(itemgetter(1), all))); } else { this.minyval = this.options.yAxis[0]; this.maxyval = this.options.yAxis[1]; this.yscale = this.maxyval - this.minyval; }};PlotKit.Layout.prototype._evaluateScales = function() { var isNil = MochiKit.Base.isUndefinedOrNull; this.xrange = this.maxxval - this.minxval; if (this.xrange == 0) this.xscale = 1.0; else this.xscale = 1/this.xrange; this.yrange = this.maxyval - this.minyval; if (this.yrange == 0) this.yscale = 1.0; else this.yscale = 1/this.yrange;};PlotKit.Layout.prototype._uniqueXValues = function() { var collapse = PlotKit.Base.collapse; var map = PlotKit.Base.map; var uniq = PlotKit.Base.uniq; var getter = MochiKit.Base.itemgetter; var items = PlotKit.Base.items; var xvalues = map(parseFloat, map(getter(0), collapse(map(getter(1), items(this.datasets))))); xvalues.sort(MochiKit.Base.compare); return uniq(xvalues);};// Create the barsPlotKit.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; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -