mirror of
https://github.com/TBEDP/datavjs.git
synced 2025-12-08 19:45:52 +00:00
1622 lines
60 KiB
JavaScript
1622 lines
60 KiB
JavaScript
/*global Raphael, d3, $, define */
|
|
/*!
|
|
* Stream的兼容定义
|
|
*/
|
|
;(function (name, definition) {
|
|
if (typeof define === 'function') { // Module
|
|
define(definition);
|
|
} else { // Assign to common namespaces or simply the global object (window)
|
|
this[name] = definition(function (id) { return this[id];});
|
|
}
|
|
})('Stream', function (require) {
|
|
var DataV = require('DataV');
|
|
var Axis = require('Axis');
|
|
|
|
/*
|
|
* Stream构造函数
|
|
* Create stream in a dom node with id "chart", width is 500; height is 600px;
|
|
* Examples:
|
|
* ```
|
|
* var stream = new Stream("chart", {"width": 500, "height": 600});
|
|
* ```
|
|
* @param {Mix} node The dom node or dom node Id
|
|
* @param {Object} options options json object for determin stream style.
|
|
*/
|
|
var Stream = DataV.extend(DataV.Chart, {
|
|
initialize: function (node, options) {
|
|
this.type = "Stream";
|
|
this.node = this.checkContainer(node);
|
|
|
|
this.level = 0;
|
|
|
|
this.defaults = {};
|
|
// Properties
|
|
this.defaults.offset = "zero";//"expand";
|
|
this.defaults.order = "default";
|
|
this.defaults.columnNameUsed = "auto";
|
|
this.defaults.rowNameUsed = "auto";
|
|
this.defaults.topInterval = 0;
|
|
this.defaults.bottomInterval = 0;
|
|
this.defaults.legend = true;
|
|
this.defaults.axis = true;
|
|
this.defaults.pathLabel = true;
|
|
this.defaults.fontSize = 12;
|
|
this.defaults.heightWidthRatio = 0.618;
|
|
//this.defaults.axisTickNumber = 8; // axis ticks number
|
|
|
|
this.defaults.indexMargin = 3; // if dates.length < indexMargin * 2 + 1, do not show label
|
|
|
|
this.userConfig = {"more": true, "max": 20, "other": 0.1};
|
|
|
|
this.timeRange = [];
|
|
// Canvas
|
|
this.defaults.width = 750;
|
|
this.defaults.height = 360;
|
|
this.defaults.totalWidth = 820;
|
|
this.defaults.naviBackWidth = 80;
|
|
this.defaults.legendHeight = 50;
|
|
this.defaults.legendWidth = 150;
|
|
this.defaults.legendIndent = 21;
|
|
this.defaults.axisHeight = 30;
|
|
this.defaults.margin = [0, 40, 0, 40];
|
|
|
|
this.defaults.customEventHandle = {"mousemove": null};
|
|
|
|
//test related
|
|
this.defaults.testMakeup = false;
|
|
this.defaults.testDays = 30;
|
|
this.defaults.testDataType = 0; //0: random; 1: false random; 2: same; >2: small change;
|
|
|
|
this.setOptions(options);
|
|
this.createCanvas();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 创建画布
|
|
*/
|
|
Stream.prototype.createCanvas = function () {
|
|
var conf = this.defaults,
|
|
canvasFatherContainer = document.createElement("div"),
|
|
coverStyle,
|
|
naviStyle,
|
|
naviTraceStyle,
|
|
naviBackStyle,
|
|
percentageStyle,
|
|
axisStyle,
|
|
brushStyle,
|
|
getBack;
|
|
|
|
this.node.style.position = "relative";
|
|
this.node.style.width = conf.totalWidth + "px";
|
|
|
|
this.legend = document.createElement("div");
|
|
//this.legendPaper = new Raphael(this.legend, conf.legendWidth - conf.legendIndent, 500);
|
|
$(this.legend).css({"overflow": "hidden",
|
|
"width": conf.legendWidth - conf.legendIndent + "px",
|
|
"padding": "10px 0 10px 0"
|
|
});
|
|
/*
|
|
legendStyle = this.legend.style;
|
|
legendStyle.overflow = "hidden";
|
|
legendStyle.width = conf.legendWidth - conf.legendIndent + "px";
|
|
*/
|
|
|
|
//legendStyle.backgroundColor = "#f4f4f4";
|
|
//legendStyle.borderWidth = "1px";
|
|
//legendStyle.borderStyle = "solid";
|
|
//legendStyle.width = conf.totalWidth + "px";
|
|
|
|
this.navi = document.createElement("div");
|
|
$(this.navi).css({
|
|
//"width": conf.totalWidth + "px",
|
|
"border-top": "1px solid #ddd",
|
|
"border-bottom": "1px solid #ddd",
|
|
//"height": "22px",
|
|
"padding-top": "5px",
|
|
"padding-bottom": "10px",
|
|
"padding-left": "10px",
|
|
"padding-right": "10px",
|
|
"font": (conf.fontSize + 1) + "px 宋体"
|
|
});
|
|
this.naviTrace = document.createElement("div");
|
|
$(this.naviTrace).css({
|
|
"width": conf.totalWidth - conf.naviBackWidth - 50 + "px",
|
|
"margin-top": "5px"
|
|
});
|
|
/*
|
|
naviTraceStyle = this.naviTrace.style;
|
|
naviTraceStyle.width = conf.totalWidth - conf.naviBackWidth - 50 + "px";
|
|
*/
|
|
this.naviBack = document.createElement("div");
|
|
this.naviBack.innerHTML = "返回上层";
|
|
$(this.naviBack).css({
|
|
"width": conf.naviBackWidth + "px",
|
|
"float": "right",
|
|
"background-color": "#f4f4f4",
|
|
"padding-top": "4px",
|
|
"padding-bottom": "4px",
|
|
"border": "1px solid #ddd",
|
|
"border-radius": "2px",
|
|
"cursor": "pointer",
|
|
"text-align": "center",
|
|
"visibility": "hidden"
|
|
});
|
|
//naviBackStyle.float = "right";
|
|
//naviBackStyle.visibility = "hidden";
|
|
this.navi.appendChild(this.naviBack);
|
|
this.navi.appendChild(this.naviTrace);
|
|
|
|
this.percentage = document.createElement("div");
|
|
if (this.userConfig.more) {
|
|
this.percentagePaper = new Raphael(this.percentage, conf.margin[3], conf.height);
|
|
}
|
|
percentageStyle = this.percentage.style;
|
|
percentageStyle.width = conf.margin[3] + "px";
|
|
percentageStyle.height = conf.height + "px";
|
|
$(this.percentage).css({
|
|
"float": "left",
|
|
"margin-bottom": "0px",
|
|
"border-bottom": "0px",
|
|
"padding-bottom": "0px"
|
|
});
|
|
|
|
this.canvasContainer = document.createElement("div");
|
|
$(this.canvasContainer).css({
|
|
"float": "left",
|
|
"width": conf.width + "px",
|
|
"margin-bottom": "0px",
|
|
"border-bottom": "0px",
|
|
"padding-bottom": "0px"
|
|
})
|
|
.append($(canvasFatherContainer).css({"position": "relative"}));
|
|
this.canvas = new Raphael(canvasFatherContainer, conf.width, conf.height);
|
|
$(this.canvasContainer).height(conf.height);
|
|
|
|
/*
|
|
this.floatTag = $("<div/>").css({
|
|
"border": "2px solid white",
|
|
"background-color": $.browser.msie ? "rgb(0, 0, 0)" : "rgba(0, 0, 0, 0.6)",
|
|
"color": "white",
|
|
"border-radius": "6px",
|
|
"padding": "8px",
|
|
"line-height": "170%",
|
|
//"opacity": 0.7,
|
|
"font-size": conf.fontSize + "px",
|
|
"font-familiy": "微软雅黑",
|
|
|
|
"visibility": "hidden",
|
|
"position": "absolute"
|
|
});
|
|
*/
|
|
this.floatTag = DataV.FloatTag()(canvasFatherContainer);
|
|
this.floatTag.css({"visibility": "hidden"});
|
|
|
|
// cover can block stream canvas when animating to prevent some default mouse event
|
|
this.cover = document.createElement("div");
|
|
coverStyle = this.cover.style;
|
|
coverStyle.position = "absolute";
|
|
coverStyle.width = conf.width + "px";
|
|
coverStyle.height = conf.height + "px";
|
|
coverStyle.zIndex = 100;
|
|
coverStyle.visibility = "hidden";
|
|
$(this.cover).bind("mousemove", {stream: this}, function (e) {
|
|
var stream = e.data.stream;
|
|
stream.coverMouse = {x: e.pageX, y: e.pageY};
|
|
});
|
|
$(this.cover).bind("mouseleave", {stream: this}, function (e) {
|
|
var stream = e.data.stream;
|
|
stream.coverMouse = undefined;
|
|
});
|
|
|
|
this.axis = document.createElement("div");
|
|
this.axisPaper = new Raphael(this.axis, conf.totalWidth - conf.legendWidth, conf.axisHeight);
|
|
//axisStyle = this.axis.style;
|
|
$(this.axis).css({
|
|
"margin-top": "0px",
|
|
"border-top": "1px solid #ddd",
|
|
"height": conf.axisHeight + "px"
|
|
});
|
|
|
|
this.leftContainer = document.createElement("div");
|
|
this.rightContainer = document.createElement("div");
|
|
|
|
this.leftContainer.appendChild(this.legend);
|
|
|
|
this.rightContainer.appendChild(this.navi);
|
|
this.middleContainer = document.createElement("div");
|
|
$(this.middleContainer).css("height", conf.height);
|
|
this.middleContainer.appendChild(this.percentage);
|
|
this.middleContainer.appendChild(this.canvasContainer);
|
|
this.middleContainer.appendChild(this.cover);
|
|
$(this.canvasFatherContainer).append(this.floatTag);
|
|
this.rightContainer.appendChild(this.middleContainer);
|
|
this.rightContainer.appendChild(this.axis);
|
|
|
|
this.node.appendChild(this.rightContainer);
|
|
this.node.appendChild(this.leftContainer);
|
|
$(this.rightContainer).css({"float": "right",
|
|
//"border": "solid 1px",
|
|
"width": conf.totalWidth - conf.legendWidth
|
|
});
|
|
$(this.leftContainer).css({ "width": conf.legendWidth - 4 + "px",
|
|
//"float": "left",
|
|
//"border": "solid 1px",
|
|
//"margin-left": "-5px",
|
|
//"height": 300,
|
|
//"max-height": 300,
|
|
//"overflow-y": "scroll",
|
|
"overflow-x": "hidden"
|
|
});
|
|
|
|
/*
|
|
this.node.appendChild(this.percentage);
|
|
this.node.appendChild(this.canvasContainer);
|
|
this.node.appendChild(this.floatTag);
|
|
this.node.appendChild(this.cover);
|
|
this.node.appendChild(this.axis);
|
|
*/
|
|
|
|
getBack = function (stream) {
|
|
stream.cover.style.visibility = "visible";
|
|
stream.coverMouse = undefined;
|
|
stream.getLevelSource();
|
|
stream.reRender();
|
|
|
|
//hidden
|
|
stream.indicatorLine.attr({"stroke": "none"});
|
|
stream.highlightLine.attr({"stroke": "none"});
|
|
stream.floatTag.css({"visibility" : "hidden"});
|
|
|
|
stream.paths.forEach(function (d, i, array) {
|
|
d.attr({transform: "s1,0.001,0,0"});
|
|
d.label.hide();
|
|
d.animate({transform: "t0,0"}, 750, "linear", function () {
|
|
stream.cover.style.visibility = "hidden";
|
|
if (typeof stream.coverMouse !== 'undefined') {
|
|
stream.indicatorLine.attr({"stroke": "#000"});
|
|
stream.highlightLine.attr({"stroke": "white"});
|
|
stream.floatTag.css({"visibility" : "visible"});
|
|
$(stream.canvas.canvas).trigger("mousemove",
|
|
[stream.coverMouse.x, stream.coverMouse.y]);
|
|
stream.coverMouse = undefined;
|
|
}
|
|
if (d.labelLoc.showLabel) {
|
|
d.label.show();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
$(this.naviTrace).on("click", ".navi", {stream: this}, function (e) {
|
|
var stream = e.data.stream;
|
|
stream.level = e.target.data.level;
|
|
getBack(stream);
|
|
});
|
|
|
|
$(this.naviBack).on("click", {stream: this}, function (e) {
|
|
var stream = e.data.stream;
|
|
stream.level -= 1;
|
|
getBack(stream);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 设置自定义选项
|
|
*/
|
|
Stream.prototype.setOptions = function (options) {
|
|
_.extend(this.defaults, options);
|
|
|
|
if (options.width) {
|
|
this.defaults.totalWidth = this.defaults.width;
|
|
this.defaults.width = this.defaults.totalWidth - this.defaults.margin[1]
|
|
- this.defaults.margin[3] - this.defaults.legendWidth;
|
|
if (!options.height) {
|
|
this.defaults.autoHeight = true;
|
|
this.defaults.height = this.defaults.width * this.defaults.heightWidthRatio;
|
|
} else {
|
|
this.defaults.autoHeight = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
Stream.prototype.hasRowName = function () {
|
|
var i,
|
|
l,
|
|
firstColumn = [],
|
|
source = this.rawData;
|
|
|
|
if ((typeof this.defaults.rowNameUsed) === "boolean") {
|
|
return this.defaults.rowNameUsed;
|
|
}
|
|
//first column from 2nd row
|
|
for (i = 1, l = source.length; i < l; i++) {
|
|
firstColumn[i] = source[i][0];
|
|
}
|
|
return !firstColumn.every(DataV.isNumeric);
|
|
};
|
|
|
|
Stream.prototype.hasColumnName = function () {
|
|
var firstRow;
|
|
if ((typeof this.defaults.columnNameUsed) === "boolean") {
|
|
return this.defaults.columnNameUsed;
|
|
}
|
|
//first row from 2nd column
|
|
firstRow = this.rawData[0].slice(1);
|
|
return !firstRow.every(DataV.isNumeric);
|
|
};
|
|
|
|
Stream.prototype.sort = function (source) {
|
|
var i, j, l, ll;
|
|
var rowSum = [];
|
|
var columnSum = [];
|
|
var newSource = [];
|
|
var rowName = [];
|
|
var that = this;
|
|
|
|
for (j = 0, ll = source[0].length; j < ll; j++) {
|
|
columnSum[j] = 0;
|
|
}
|
|
|
|
for (i = 0, l = source.length; i < l; i++) {
|
|
rowSum[i] = 0;
|
|
for (j = 0, ll = source[0].length; j < ll; j++) {
|
|
rowSum[i] += source[i][j];
|
|
columnSum[j] += source[i][j];
|
|
}
|
|
rowSum[i] = [rowSum[i]];
|
|
rowSum[i].index = i;
|
|
}
|
|
|
|
rowSum.sort(function (a, b) {
|
|
return b[0] - a[0];
|
|
});
|
|
|
|
rowSum.forEach(function (d, i) {
|
|
newSource[i] = source[d.index];
|
|
if (that.rowName) {
|
|
rowName[i] = that.rowName[d.index];
|
|
}
|
|
});
|
|
|
|
for (i = 0, l = rowSum.length; i < l; i++) {
|
|
rowSum[i] = rowSum[i][0];
|
|
}
|
|
|
|
//this.mergeOthe
|
|
|
|
this.rowName = rowName;
|
|
this.rowSum = rowSum;
|
|
this.columnSum = columnSum;
|
|
this.total = d3.sum(this.rowSum);
|
|
|
|
return newSource;
|
|
};
|
|
|
|
Stream.prototype.mergeOther = function (source) {
|
|
//change digitData, rowSum, rowName;
|
|
};
|
|
|
|
Stream.prototype.getDigitData = function (source) {
|
|
//get first column name, row name and digitData;
|
|
var conf = this.defaults,
|
|
firstRow = source[0],
|
|
firstColumn,
|
|
digitData;
|
|
|
|
var i, j, l, ll;
|
|
|
|
firstColumn = source.map(function (d) {
|
|
return d[0];
|
|
});
|
|
|
|
if (this.hasRowName()) {
|
|
if (this.hasColumnName()) {
|
|
//row names, column names
|
|
this.rowName = firstColumn.slice(1);
|
|
this.columnName = firstRow.slice(1);
|
|
digitData = source.map(function (d) {
|
|
return d.slice(1);
|
|
}).slice(1);
|
|
} else {
|
|
//row names, no column names
|
|
this.rowName = firstColumn;
|
|
this.columnName = undefined;
|
|
digitData = source.map(function (d) {
|
|
return d.slice(1);
|
|
});
|
|
}
|
|
} else {
|
|
if (this.hasColumnName()) {
|
|
//no row names, column names
|
|
this.rowName = undefined;
|
|
this.columnName = firstRow;
|
|
digitData = source.slice(1);
|
|
} else {
|
|
//no row names, no column names
|
|
if (conf.columnNameUsed === "auto" && conf.rowNameUsed === "auto" && !isNumber(source[0][0])) {
|
|
throw new Error("Please specify whether there are column names or row names");
|
|
}
|
|
this.rowName = undefined;
|
|
this.columnName = undefined;
|
|
digitData = source;
|
|
}
|
|
}
|
|
for (i = 0, l = digitData.length; i < l; i++) {
|
|
for (j = 0, ll = digitData[0].length; j < ll; j++) {
|
|
digitData[i][j] = parseFloat(digitData[i][j]);
|
|
}
|
|
}
|
|
return digitData;
|
|
};
|
|
|
|
Stream.prototype.getInfo = function () {
|
|
var allInfos = [];
|
|
var i, j, l, ll;
|
|
var infos, info;
|
|
var column;
|
|
var digitData = this.digitData;
|
|
var descending = function (a, b) {
|
|
return b.value - a.value;
|
|
};
|
|
for (i = 0, l = this.digitData.length; i < l; i++) {
|
|
infos = allInfos[i] = [];
|
|
infos.ratio = this.rowSum[i] / this.total;
|
|
infos.value = this.rowSum[i];
|
|
infos.name = this.rowName[i];
|
|
infos.id = i;
|
|
}
|
|
for (i = 0, l = digitData.length; i < l; i++) {
|
|
column = [];
|
|
for (j = 0, ll = digitData[0].length; j < ll; j++) {
|
|
allInfos[i][j] = column[j] = {
|
|
/********************/
|
|
"date": this.columnName[j],
|
|
"id": i,
|
|
"name": allInfos[i].name,
|
|
"tip": "<b>" + allInfos[i].name + "</b><br/>占比:"
|
|
+ (Math.round(digitData[i][j] / this.columnSum[j] * 10000) / 100) + "%<br/>",
|
|
"total": allInfos[i].ratio,
|
|
//"value": columnTotal[i]
|
|
/*****************/
|
|
"value" : digitData[i][j],
|
|
"index" : j,
|
|
"rowInfo" : allInfos[i],
|
|
"ratio" : digitData[i][j] / this.columnSum[j]
|
|
};
|
|
}
|
|
|
|
column.sort(descending);
|
|
|
|
for (j = 0, ll = column.length; j < ll; j++) {
|
|
column[j].rank = j;
|
|
}
|
|
}
|
|
return allInfos;
|
|
};
|
|
|
|
/**
|
|
* @param source The data source.
|
|
* @example
|
|
* // 例如下面的数组表示2个人在一年4个季度的消费。第一个人在4个季度里消费了1、2、3、9元。第二个人消费了3、4、6、3元。
|
|
* [
|
|
* [1,2,3,9],
|
|
* [3,4,6,3]
|
|
* ]
|
|
*/
|
|
Stream.prototype.setSource = function (source) {
|
|
/*
|
|
var newSource = this.sourceChange(source);
|
|
var conf = this.defaults,
|
|
firstRow = source[0],
|
|
firstColumn,
|
|
digitData,
|
|
i,
|
|
l;
|
|
*/
|
|
this.rawData = source;
|
|
this.digitData = this.getDigitData(this.rawData);
|
|
|
|
//get date, sort and allInfos;
|
|
//date
|
|
this.date = source[0].slice(1, source[0].length);
|
|
this.timeRange = [0, this.date.length - 1];
|
|
//sort
|
|
this.digitData = this.sort(this.digitData);
|
|
//merge other
|
|
/*
|
|
if (this.userConfig.more && this.userConfig.other > 0) {
|
|
this.digitData = this.mergeOther(this.digitData);
|
|
}
|
|
*/
|
|
//allInfos;
|
|
this.allInfos = this.getInfo(this.digitData);
|
|
|
|
this.level = 0;
|
|
this.getLevelSource();
|
|
//this.source = this.remapSource(digitData);
|
|
this.canAnimate = false;
|
|
};
|
|
|
|
//if useSting is true, start and end are date string, else start and end are index number;
|
|
Stream.prototype.setTimeRange = function (start, end, useString) {
|
|
var findIndex = function (arr, value) {
|
|
var ctr = "";
|
|
var i;
|
|
for (i = 0; i < arr.length; i++) {
|
|
// use === to check for Matches. ie., identical (===), ;
|
|
if (arr[i] === value) {
|
|
return i;
|
|
}
|
|
}
|
|
return ctr;
|
|
};
|
|
|
|
var idx1, idx2, temp;
|
|
if (useString) {
|
|
idx1 = findIndex(this.date, start);
|
|
if (idx1 === "") {
|
|
throw new Error(start + " is not found");
|
|
}
|
|
idx2 = findIndex(this.date, end);
|
|
if (idx2 === "") {
|
|
throw new Error(end + " is not found");
|
|
}
|
|
} else {
|
|
idx1 = start;
|
|
idx2 = end;
|
|
}
|
|
if (idx1 > idx2) {
|
|
temp = idx1;
|
|
idx1 = idx2;
|
|
idx2 = temp;
|
|
}
|
|
if (idx1 === idx2) {
|
|
throw new Error("start index and end index can not be same.");
|
|
}
|
|
if (idx2 > this.date.length - 1) {
|
|
throw new Error("start index or end index is beyond the time range.");
|
|
}
|
|
this.timeRange = [idx1, idx2];
|
|
this.getLevelSource();
|
|
};
|
|
|
|
Stream.prototype.getDataByTimeRange = function () {
|
|
if (this.timeRange[0] === 0 && this.timeRange[1] === this.date.length - 1) {
|
|
return this.digitData;
|
|
} else {
|
|
var data = [];
|
|
var tr = this.timeRange;
|
|
this.digitData.forEach(function (d, i) {
|
|
data[i] = d.slice(tr[0], tr[1] + 1);
|
|
});
|
|
return data;
|
|
}
|
|
};
|
|
|
|
Stream.prototype.getLevelSource = function () {
|
|
var data = this.getDataByTimeRange(),//this.digitData,
|
|
rowStart = this.level * (this.userConfig.max - 1),
|
|
rowEnd,
|
|
needMoreRow,
|
|
column = data[0].length,
|
|
remap = [],
|
|
i,
|
|
j,
|
|
k,
|
|
m,
|
|
moreRow,
|
|
moreSum,
|
|
totalSum,
|
|
infos = [],
|
|
moreRowInfo = [];
|
|
|
|
if (column < 1) {
|
|
throw new Error("Data source is empty.");
|
|
}
|
|
if (this.userConfig.more) {
|
|
if (rowStart + this.userConfig.max >= data.length) {
|
|
if (rowStart + this.userConfig.max === data.length && this.allInfos[data.length - 1][0].id === -2) {
|
|
//last more sum < this.userConfig.other
|
|
rowEnd = rowStart + this.userConfig.max - 1;
|
|
needMoreRow = true;
|
|
} else {
|
|
rowEnd = data.length;
|
|
needMoreRow = false;
|
|
}
|
|
} else {
|
|
rowEnd = rowStart + this.userConfig.max - 1;
|
|
needMoreRow = true;
|
|
}
|
|
} else {
|
|
rowStart = 0;
|
|
rowEnd = data.length;
|
|
needMoreRow = false;
|
|
}
|
|
for (i = rowStart; i < rowEnd; i++) {
|
|
k = i - rowStart;
|
|
remap[k] = [];
|
|
for (j = 0; j < column; j++) {
|
|
remap[k][j] = {};
|
|
remap[k][j].x = j;
|
|
remap[k][j].y = parseFloat(data[i][j]);
|
|
}
|
|
if (this.timeRange[0] === 0 && this.timeRange[1] === this.date.length - 1) {
|
|
infos[k] = this.allInfos[i];
|
|
} else {
|
|
infos[k] = this.allInfos[i].slice(this.timeRange[0], this.timeRange[1] + 1);
|
|
}
|
|
}
|
|
if (needMoreRow) {
|
|
if (rowStart + this.userConfig.max === data.length && this.allInfos[data.length - 1][0].id === -2) {
|
|
//last more sum < this.userConfig.other
|
|
var valueArray = data[data.length - 1];
|
|
moreRow = [];
|
|
for (j = 0; j < column; j++) {
|
|
moreRow[j] = {};
|
|
moreRow[j].x = j;
|
|
moreRow[j].y = valueArray[j];
|
|
}
|
|
moreRowInfo = this.allInfos[data.length - 1];
|
|
} else {
|
|
moreRow = [];
|
|
for (j = 0; j < column; j++) {
|
|
moreSum = 0;
|
|
totalSum = 0;
|
|
for (m = data.length - 1; m >= rowEnd; m--) {
|
|
moreSum += parseFloat(data[m][j]);
|
|
totalSum += parseFloat(this.allInfos[m][j].total);
|
|
}
|
|
moreRow[j] = {};
|
|
moreRow[j].x = j;
|
|
moreRow[j].y = moreSum;
|
|
moreRowInfo[j] = {
|
|
"date": this.allInfos[0][j].date,
|
|
"id": -1,// -1 clickable; -2 not click
|
|
"name": "更多",
|
|
"tip": "<b>更多</b><br/>占比:" + (Math.round(moreSum * 10000) / 100) + "%<br/>点击查看更多信息<br/>",
|
|
"total": totalSum,
|
|
"value": moreSum
|
|
};
|
|
}
|
|
}
|
|
remap = [moreRow].concat(remap);
|
|
infos = [moreRowInfo].concat(infos);
|
|
}
|
|
this.infos = infos;
|
|
this.source = remap;
|
|
};
|
|
|
|
/*
|
|
Stream.prototype.remapSource = function (data) {
|
|
var row = data.length,
|
|
column = data[0].length,
|
|
remap = [],
|
|
i,
|
|
j;
|
|
if (column < 1) {
|
|
throw new Error("Data source is empty.");
|
|
}
|
|
for (i = 0; i < row; i++) {
|
|
remap[i] = [];
|
|
for (j = 0; j < column; j++) {
|
|
remap[i][j] = {};
|
|
remap[i][j].x = j;
|
|
remap[i][j].y = parseFloat(data[i][j]);
|
|
}
|
|
}
|
|
return remap;
|
|
};
|
|
*/
|
|
|
|
Stream.prototype.layout = function () {
|
|
var conf = this.defaults;
|
|
d3.layout.stack().offset(conf.offset).order(conf.order)(this.source);
|
|
};
|
|
|
|
Stream.prototype.getColor = function (colorJson) {
|
|
var colorMatrix = DataV.getColor();
|
|
var color;
|
|
var colorStyle = colorJson || {};
|
|
var colorMode = colorStyle.mode || 'default';
|
|
var i, l;
|
|
|
|
switch (colorMode) {
|
|
case "gradient":
|
|
l = this.source.length;
|
|
var colorL = Math.round(l / 5);
|
|
if (colorL > colorMatrix.length - 1) {
|
|
colorL = colorMatrix.length - 1;
|
|
}
|
|
var testColor = [colorMatrix[0][0], colorMatrix[colorL][0]];
|
|
var test1 = DataV.gradientColor(testColor, "special");
|
|
var testColorMatrix = [];
|
|
var testColorMatrix1 = [];
|
|
for (i = 0; i < l; i++) {
|
|
testColorMatrix.push([test1(i / (l - 1)), test1(i / (l - 1))]);
|
|
}
|
|
|
|
for (i = (l - 1); i >= 0; i--) {
|
|
testColorMatrix1.push(testColorMatrix[i]);
|
|
}
|
|
|
|
colorMatrix = testColorMatrix;
|
|
|
|
break;
|
|
case "random":
|
|
case "default":
|
|
break;
|
|
}
|
|
|
|
var ratio = colorStyle.ratio || 0;
|
|
if (ratio < 0) { ratio = 0; }
|
|
if (ratio > 1) { ratio = 1; }
|
|
var colorArray = [];
|
|
for (i = 0, l = colorMatrix.length; i < l; i++) {
|
|
var colorFunc = d3.interpolateRgb.apply(null, [colorMatrix[i][0], colorMatrix[i][1]]);
|
|
colorArray.push(colorFunc(ratio));
|
|
}
|
|
color = d3.scale.ordinal().range(colorArray);
|
|
|
|
return color;
|
|
};
|
|
|
|
Stream.prototype.generatePaths = function () {
|
|
this.createNavi();
|
|
|
|
this.createPercentage();
|
|
|
|
this.createAxis();
|
|
|
|
this.createStreamPaths();
|
|
|
|
this.createLegend();
|
|
};
|
|
|
|
Stream.prototype.createLegend = function () {
|
|
var conf = this.defaults,
|
|
//paper = this.legendPaper,
|
|
legends = [],
|
|
m = [10, 20, 10, 20],
|
|
left = m[3],
|
|
top = m[0],
|
|
lineHeight = 25,
|
|
legendInterval = 10,
|
|
width = conf.legendWidth - conf.legendIndent,
|
|
r0 = 5,
|
|
r1 = 7,
|
|
circleW = 18,
|
|
x,
|
|
y,
|
|
circle,
|
|
text,
|
|
box,
|
|
ul,
|
|
li,
|
|
color = this.getColor({mode: conf.colorMode}),
|
|
i,
|
|
l,
|
|
leftHeight,
|
|
legendHeight,
|
|
legendTopMargin,
|
|
hoverIn = function (e) {
|
|
/*
|
|
var index = this.data("index");
|
|
var stream = this.data("stream");
|
|
var r = this.data("r1");
|
|
var path = stream.paths[index];
|
|
//stream.legends[stream.preIndex]
|
|
stream.preIndex = index;
|
|
stream.legends[index].circle.animate({"r": r, "opacity": 0.5}, 300);
|
|
path.attr({"opacity": 0.5});
|
|
*/
|
|
var index = e.data.index;
|
|
var stream = e.data.stream;
|
|
var path = stream.paths[index];
|
|
//stream.legends[stream.preIndex]
|
|
stream.preIndex = index;
|
|
stream.legends[index].css({"background": "#dddddd"});
|
|
path.attr({"opacity": 0.5});
|
|
},
|
|
hoverOut = function (e) {
|
|
/*
|
|
var index = this.data("index");
|
|
var stream = this.data("stream");
|
|
var r = this.data("r0");
|
|
var path = stream.paths[index];
|
|
stream.preIndex = index;
|
|
stream.legends[index].circle.animate({"r": r, "opacity": 1}, 300);
|
|
path.attr({"opacity": 1.0});
|
|
*/
|
|
var index = e.data.index;
|
|
var stream = e.data.stream;
|
|
var path = stream.paths[index];
|
|
stream.preIndex = index;
|
|
stream.legends[index].css({"background": "white"});
|
|
path.attr({"opacity": 1.0});
|
|
};
|
|
|
|
ul = $("<ul/>");
|
|
ul.css({
|
|
"margin": "0px 0px 0px 10px",
|
|
"padding-left": "0px"
|
|
});
|
|
$(this.legend).append(ul);
|
|
|
|
for (i = 0, l = this.infos.length; i < l; i++) {
|
|
li = $("<li>" + "<span style=\"color: black\">" + this.infos[i][0].name + "</span>" + "</li>");
|
|
li.css({"list-style-type": "square",
|
|
"list-style-position": "inside",
|
|
//"background": "gray",
|
|
"color": color(i),
|
|
//"display": "inline",
|
|
"white-space": "nowrap",
|
|
"padding-left": 5
|
|
});
|
|
ul.append(li);
|
|
li.mouseenter({"index": i, "stream": this}, hoverIn);
|
|
li.mouseleave({"index": i, "stream": this}, hoverOut);
|
|
legends.push(li);
|
|
/*
|
|
text = paper.text(0, 0, this.infos[i][0].name)
|
|
.attr({"font-size": conf.fontSize,
|
|
"text-anchor": "start",
|
|
"font-family": "微软雅黑"});
|
|
box = text.getBBox();
|
|
//if (left + circleW + box.width >= width - m[1]) {
|
|
//new line
|
|
left = m[3];
|
|
top += lineHeight;
|
|
//}
|
|
circle = paper.circle(left + circleW / 2, top + lineHeight / 2, r0)
|
|
.attr({"stroke": "none", "fill": color(i)})
|
|
.data("index", i)
|
|
.data("stream", this)
|
|
.data("r0", r0)
|
|
.data("r1", r1)
|
|
.hover(hoverIn, hoverOut);
|
|
text.transform("t" + (left + circleW) + "," + (top + lineHeight / 2));
|
|
paper.rect(left + circleW, top, box.width, lineHeight)
|
|
.attr({"stroke": "none",
|
|
"fill": "#000",
|
|
"opacity": 0})
|
|
.data("index", i)
|
|
.data("stream", this)
|
|
.data("r0", r0)
|
|
.data("r1", r1)
|
|
.hover(hoverIn, hoverOut);
|
|
|
|
legends.push({"text": text, "circle": circle});
|
|
|
|
left += legendInterval + circleW + box.width;
|
|
*/
|
|
}
|
|
this.legends = legends;
|
|
//paper.setSize(width, top + lineHeight + m[2]);
|
|
|
|
//height and margin
|
|
leftHeight = $(this.rightContainer).height();
|
|
legendHeight = $(this.legend).height();
|
|
$(this.leftContainer).css({
|
|
"height": leftHeight
|
|
});
|
|
if (leftHeight > legendHeight) {
|
|
$(this.legend).css({"margin-top": leftHeight - legendHeight - 30});
|
|
} else {
|
|
$(this.legend).css({"margin-top": 0});
|
|
}
|
|
};
|
|
|
|
Stream.prototype.createNavi = function () {
|
|
if (!this.userConfig.more) {
|
|
$(this.navi).css({"visibility": "hidden",
|
|
"position": "absolute"
|
|
});
|
|
} else {
|
|
$(this.navi).css({"visibility": "visible",
|
|
"position": "relative"
|
|
});
|
|
}
|
|
var i,
|
|
span;
|
|
$(this.naviTrace).empty();
|
|
for (i = 0; i <= this.level; i++) {
|
|
$(this.naviTrace).append($("<span> > </span>"));
|
|
span = document.createElement("span");
|
|
span.data = {level: i};
|
|
span = $(span)
|
|
.html(i === 0 ? "第1层"/*this.userConfig.rootName*/ : "第" + (i + 1) + "层")
|
|
.appendTo($(this.naviTrace));
|
|
if (i !== this.level) {
|
|
span.css({"cursor": "pointer", "color": "#1E90FF"})
|
|
.addClass("navi");
|
|
//.data("level", i);
|
|
}
|
|
}
|
|
if (this.level > 0) {
|
|
this.naviBack.style.visibility = "visible";
|
|
} else {
|
|
this.naviBack.style.visibility = "hidden";
|
|
}
|
|
};
|
|
|
|
Stream.prototype.getMaxPercentage = function () {
|
|
this.maxPercentage = this.allInfos.reduce(function (a, b, i, array) {
|
|
return [{total: a[0].total + b[0].total}];
|
|
})[0].total;
|
|
};
|
|
|
|
Stream.prototype.createPercentage = function () {
|
|
if (!this.userConfig.more) {
|
|
return;
|
|
}
|
|
var conf = this.defaults;
|
|
var maxY = this.getMaxY(),
|
|
y;
|
|
if (this.firstRender) {
|
|
this.getMaxPercentage();
|
|
}
|
|
|
|
maxY /= this.maxPercentage;
|
|
y = maxY > 0.1 ? (1 - maxY) * conf.height + conf.fontSize * 2 / 3
|
|
: (1 - maxY) * conf.height - conf.fontSize * 2 / 3;
|
|
|
|
if (this.firstRender) {
|
|
this.percentageRect = this.percentagePaper.rect(0, (1 - maxY) * conf.height,
|
|
conf.margin[3], maxY * conf.height)
|
|
.attr({"fill": "#f4f4f4", "stroke": "#aaa", "stroke-width": 0.5});
|
|
this.percentageText = this.percentagePaper.text(conf.margin[3] / 2, y,
|
|
Math.round(maxY * 100) + "%")
|
|
.attr({"text-anchor": "middle"});
|
|
} else {
|
|
this.percentageRect.animate({"y": (1 - maxY) * conf.height, "height": maxY * conf.height}, 750);
|
|
this.percentageText.attr({"text": Math.round(maxY * 100) + "%"})
|
|
.animate({"y": y}, 750);
|
|
}
|
|
};
|
|
|
|
Stream.prototype.createStreamPaths = function () {
|
|
var canvas = this.canvas,
|
|
paths = [],
|
|
labels = [],
|
|
area = this.generateArea(),
|
|
color = this.getColor({mode: this.defaults.colorMode}),
|
|
conf = this.defaults,
|
|
i,
|
|
l,
|
|
_area,
|
|
pathLegend,
|
|
path,
|
|
pathLegendMouseOver = function () {
|
|
var path = this.path,
|
|
anchorIndex = path.index;
|
|
path.paths.forEach(function (d, i, array) {
|
|
if (i !== anchorIndex) {
|
|
array[i].attr({"fill": d3.interpolateRgb.apply(null, [array[i].color, "#fff"])(0.5)});
|
|
}
|
|
});
|
|
this.style.backgroundColor = d3.interpolateRgb.apply(null, [path.color, "#fff"])(0.8);
|
|
},
|
|
|
|
pathLegendMouseOut = function () {
|
|
var path = this.path,
|
|
anchorIndex = path.index;
|
|
path.paths.forEach(function (d, i, array) {
|
|
if (i !== anchorIndex) {
|
|
array[i].attr({"fill": array[i].color});
|
|
}
|
|
});
|
|
path.legend.style.backgroundColor = path.color;
|
|
},
|
|
|
|
getLabelLocation = function (locArray, el) {
|
|
var x = 0,
|
|
y = 0,
|
|
i;
|
|
var ratioMargin = 0.15;
|
|
var index = 0;
|
|
var max = 0;
|
|
var box = el.getBBox();
|
|
var xInterval;
|
|
var minTop, maxBottom;
|
|
var showLabel = true;
|
|
var loc;
|
|
var height;
|
|
|
|
xInterval = Math.ceil(box.width / (locArray[1].x - locArray[0].x) / 2);
|
|
if (xInterval === 0) {
|
|
xInterval = 1;
|
|
}
|
|
|
|
locArray.forEach(function (d, i, array) {
|
|
var m = Math.max(ratioMargin * array.length, xInterval);
|
|
if (i >= m && i <= array.length - m) {
|
|
if (d.y > max) {
|
|
minTop = d.y0 - d.y;
|
|
maxBottom = d.y0;
|
|
max = d.y;
|
|
index = i;
|
|
}
|
|
}
|
|
});
|
|
for (i = index - xInterval; i <= index + xInterval; i++) {
|
|
if (i < 0 || i >= locArray.length) {
|
|
height = 0;
|
|
showLabel = false;
|
|
break;
|
|
//return;
|
|
}
|
|
loc = locArray[i];
|
|
//top's y is small
|
|
if (loc.y0 - loc.y > minTop) {
|
|
minTop = loc.y0 - loc.y;
|
|
}
|
|
if (loc.y0 < maxBottom) {
|
|
maxBottom = loc.y0;
|
|
}
|
|
}
|
|
|
|
if (showLabel && maxBottom - minTop >= box.height * 0.8) {
|
|
x = locArray[index].x;
|
|
y = (minTop + maxBottom) / 2;
|
|
//y = locArray[index].y0 - locArray[index].y / 2;
|
|
} else {
|
|
showLabel = false;
|
|
}
|
|
/*
|
|
x = locArray[index].x;
|
|
y = locArray[index].y0 - locArray[index].y / 2;
|
|
for (i = index - indexMargin; i <= index + indexMargin; i++) {
|
|
height += locArray[i].y;
|
|
}
|
|
height = height / (2 * indexMargin + 1);
|
|
*/
|
|
|
|
return {x: x,
|
|
y: y,
|
|
showLabel: showLabel};
|
|
},
|
|
|
|
getLabelLocation_old2 = function (locArray, conf) {
|
|
var x, y, height = 0, i;
|
|
var indexMargin = Math.min(conf.indexMargin, Math.floor((locArray.length - 1) / 2));
|
|
var ratioMargin = 0.15;
|
|
var index = indexMargin;
|
|
var max = 0;
|
|
if (locArray.length >= conf.indexMargin * 2 + 1) {
|
|
locArray.forEach(function (d, i, array) {
|
|
var m = Math.max(indexMargin, ratioMargin * array.length);
|
|
if (i >= m && i <= array.length - m) {
|
|
if (d.y > max) {
|
|
max = d.y;
|
|
index = i;
|
|
}
|
|
}
|
|
});
|
|
x = locArray[index].x;
|
|
y = locArray[index].y0 - locArray[index].y / 2;
|
|
for (i = index - indexMargin; i <= index + indexMargin; i++) {
|
|
height += locArray[i].y;
|
|
}
|
|
height = height / (2 * indexMargin + 1);
|
|
} else {
|
|
x = -100;
|
|
y = -100;
|
|
height = 0;
|
|
}
|
|
|
|
return {x: x,
|
|
y: y,
|
|
height: height};
|
|
};
|
|
|
|
canvas.rect(0, 0, conf.width, conf.height)
|
|
.attr({"stroke": "none",
|
|
"fill": "#e0e0e0"});
|
|
for (i = 0, l = this.source.length; i < l; i++) {
|
|
_area = area(this.pathSource[i]);
|
|
path = canvas.path(_area).attr({fill: color(i),
|
|
stroke: color(i),
|
|
"stroke-width": 1,
|
|
"stroke-linejoin": "round",
|
|
"transform": "t0," + conf.topInterval
|
|
});
|
|
path.color = color(i);
|
|
path.index = i;
|
|
path.info = this.infos[i];
|
|
|
|
path.paths = paths;
|
|
path.topTrans = conf.topInterval;
|
|
path.bottomTrans = conf.bottomInterval;
|
|
path.stream = this;
|
|
|
|
path.node.streamPath = path;
|
|
path.node.setAttribute("class", "streamPath rvml");
|
|
|
|
//path.click(pathClick);
|
|
//path.mouseover(pathMouseOver);
|
|
//path.mouseout(pathMouseOut);
|
|
//path.mousemove(pathMouseMove);
|
|
|
|
paths[path.index] = path;
|
|
//brush canvas background
|
|
}
|
|
|
|
//label
|
|
for (i = 0, l = paths.length; i < l; i++) {
|
|
path = paths[i];
|
|
path.label = this.canvas.text(0, 0,
|
|
conf.pathLabel ?
|
|
path.info[0].name + " " + (Math.round(path.info[0].total * 10000) / 100) + "%" : "")
|
|
.attr({"text-anchor": "middle",
|
|
"fill": "white",
|
|
"font-size": conf.fontSize,
|
|
"font-family": "微软雅黑"});
|
|
path.labelLoc = getLabelLocation(this.pathSource[i], path.label);
|
|
/*
|
|
path.labelLoc = getLabelLocation(this.pathSource[i], conf);
|
|
path.label = this.canvas.text(path.labelLoc.x, path.labelLoc.y,
|
|
path.info[0].name + " " + (Math.round(path.info[0].total * 10000) / 100) + "%")
|
|
.attr({"text-anchor": "middle",
|
|
"fill": "white",
|
|
"font-size": conf.fontSize,
|
|
"font-family": "微软雅黑"});
|
|
if(path.labelLoc.height <= 20){
|
|
path.labelOpacity = 0;
|
|
path.label.attr({"opacity": 0});
|
|
} else {
|
|
path.labelOpacity = 1;
|
|
}
|
|
*/
|
|
if (path.labelLoc.showLabel) {
|
|
path.label.attr({"x": path.labelLoc.x,
|
|
"y": path.labelLoc.y});
|
|
} else {
|
|
path.label.attr({"opacity": 0});
|
|
//path.labelOpacity = 1;
|
|
}
|
|
if (i === 0 && path.info[0].id === -1) {
|
|
path.attr({"cursor": "pointer"});
|
|
path.label.attr({"cursor": "pointer"});
|
|
}
|
|
labels.push(path.label);
|
|
path.label.node.setAttribute("class", "streamPath rvml");
|
|
}
|
|
|
|
$(this.canvas.canvas).unbind();
|
|
|
|
var mouseenter = function (e) {
|
|
var stream = e.data.stream;
|
|
stream.indicatorLine.attr({"stroke": "#000"});
|
|
stream.highlightLine.attr({"stroke": "white"});
|
|
stream.floatTag.css({"visibility" : "visible"});
|
|
stream.axisPopText.show();
|
|
stream.axisPopBubble.show();
|
|
};
|
|
|
|
var mouseleave = function (e) {
|
|
var stream = e.data.stream,
|
|
circle;
|
|
stream.indicatorLine.attr({"stroke": "none"});
|
|
stream.highlightLine.attr({"stroke": "none"});
|
|
stream.floatTag.css({"visibility" : "hidden"});
|
|
stream.axisPopText.hide();
|
|
stream.axisPopBubble.hide();
|
|
//recover prepath;
|
|
if (typeof stream.prePath !== 'undefined') {
|
|
stream.prePath.attr({"opacity": 1, "stroke-width": 1});
|
|
// set legend
|
|
//circle = stream.legends[stream.prePath.index].circle;
|
|
//circle.attr({"r": circle.data("r0"), "opacity": 1});
|
|
stream.legends[stream.prePath.index].css({"background": "white"});
|
|
stream.prePath = undefined;
|
|
}
|
|
};
|
|
|
|
var click = function (e) {
|
|
var stream = e.data.stream,
|
|
position;
|
|
if (typeof stream.prePath !== 'undefined'
|
|
&& stream.prePath.info[0].id === -1) {
|
|
|
|
//hidden
|
|
stream.indicatorLine.attr({"stroke": "none"});
|
|
stream.highlightLine.attr({"stroke": "none"});
|
|
stream.floatTag.css({"visibility" : "hidden"});
|
|
|
|
stream.level += 1;
|
|
|
|
//set cover
|
|
position = $(this).parent().position();
|
|
$(stream.cover).css({left: position.left + "px",
|
|
top: position.top + "px"});
|
|
stream.cover.style.visibility = "visible";
|
|
stream.coverMouse = {x: e.pageX, y: e.pageY};
|
|
|
|
//redraw
|
|
stream.getLevelSource();
|
|
stream.reRender();
|
|
|
|
//hidden
|
|
stream.indicatorLine.attr({"stroke": "none"});
|
|
stream.highlightLine.attr({"stroke": "none"});
|
|
stream.floatTag.css({"visibility" : "hidden"});
|
|
|
|
stream.paths.forEach(function (d, i, array) {
|
|
d.attr({transform: "s1,0.001,0," + stream.defaults.height});
|
|
d.label.hide();
|
|
d.animate({transform: "t0,0"}, 750, "linear", function () {
|
|
stream.cover.style.visibility = "hidden";
|
|
if (typeof stream.coverMouse !== 'undefined') {
|
|
stream.indicatorLine.attr({"stroke": "#000"});
|
|
stream.highlightLine.attr({"stroke": "white"});
|
|
stream.floatTag.css({"visibility" : "visible"});
|
|
$(stream.canvas.canvas).trigger("mousemove",
|
|
[stream.coverMouse.x, stream.coverMouse.y]);
|
|
stream.coverMouse = undefined;
|
|
}
|
|
//if (d.labelOpacity === 1)
|
|
if (d.labelLoc.showLabel) {
|
|
d.label.show();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
var mousemove = function (e, pageX, pageY) {
|
|
var stream = e.data.stream;
|
|
var offset = $(this).parent().offset();
|
|
var position = $(this).parent().position();
|
|
//var offset = $(this).offset();
|
|
var x = (e.pageX || pageX) - offset.left,
|
|
y = (e.pageY || pageY) - offset.top;
|
|
var floatTag,
|
|
floatTagWidth,
|
|
floatTagHeight,
|
|
mouseToFloatTag = {x: 20, y: 20};
|
|
var path,
|
|
pathSource = stream.pathSource,
|
|
pathSourceP,
|
|
pathIndex,
|
|
circle;
|
|
var i, l;
|
|
var xIdx = Math.floor((x / (stream.defaults.width
|
|
/ (stream.source[0].length - 1) / 2) + 1) / 2);
|
|
var pathsourceP,
|
|
lineX;
|
|
|
|
//get path
|
|
path = undefined;
|
|
pathSource = stream.pathSource;
|
|
for (i = 0, l = pathSource.length; i < l; i++) {
|
|
if (y >= pathSource[i][xIdx].y0 - pathSource[i][xIdx].y && y <= pathSource[i][xIdx].y0) {
|
|
path = stream.paths[i];
|
|
pathIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (typeof path === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
|
|
//recover prepath;
|
|
if (typeof stream.prePath !== 'undefined') {
|
|
stream.prePath.attr({"opacity": 1, "stroke-width": 1});
|
|
// set legend
|
|
/*
|
|
//circle = stream.legends[stream.prePath.index].circle;
|
|
//circle.attr({"r": circle.data("r0"), "opacity": 1});
|
|
*/
|
|
stream.legends[stream.prePath.index].css({"background": "white"});
|
|
}
|
|
//change new path;
|
|
stream.prePath = path;
|
|
path.attr({"opacity": 0.5, "stroke-width": 0});
|
|
|
|
// set legend
|
|
/*
|
|
circle = stream.legends[stream.prePath.index].circle;
|
|
circle.attr({"r": circle.data("r1"), "opacity": 0.5});
|
|
*/
|
|
stream.legends[stream.prePath.index].css({"background": "#dddddd"});
|
|
|
|
//set indicator and highlight line
|
|
lineX = stream.defaults.width * xIdx / (stream.source[0].length - 1);
|
|
pathSourceP = pathSource[pathSource.length - 1][xIdx];
|
|
stream.indicatorLine.attr({path: "M" + lineX
|
|
+ " " + (pathSourceP.y0 - pathSourceP.y)
|
|
+ "V" + pathSource[0][xIdx].y0});
|
|
|
|
pathSourceP = pathSource[pathIndex][xIdx];
|
|
stream.highlightLine.attr({path: "M" + lineX
|
|
+ " " + (pathSourceP.y0 - pathSourceP.y)
|
|
+ "V" + pathSourceP.y0});
|
|
if (pathIndex === 0 && path.info[0].id === -1) {
|
|
stream.highlightLine.attr({"cursor": "pointer"});
|
|
} else {
|
|
stream.highlightLine.attr({"cursor": "auto"});
|
|
}
|
|
|
|
floatTag = stream.floatTag;
|
|
floatTag.html(path.info[xIdx].tip);
|
|
|
|
//axis pop bubble
|
|
stream.axisPopText.attr({"text": stream.date[xIdx + stream.timeRange[0]]})
|
|
.transform("t" + (lineX + stream.defaults.margin[3]) + ",0");
|
|
stream.axisPopBubble.transform("t" + (lineX + stream.defaults.margin[3]) + ",0");
|
|
|
|
//customevent;
|
|
if (stream.defaults.customEventHandle.mousemove) {
|
|
stream.defaults.customEventHandle.mousemove.call(stream,
|
|
{"timeIndex": xIdx, "pathIndex": pathIndex});
|
|
}
|
|
};
|
|
|
|
$(this.canvas.canvas).bind("mouseenter", {"stream": this}, mouseenter);
|
|
|
|
$(this.canvas.canvas).bind("mouseleave", {"stream": this}, mouseleave);
|
|
|
|
$(this.canvas.canvas).bind("click", {"stream": this}, click);
|
|
|
|
$(this.canvas.canvas).bind("mousemove", {"stream": this}, mousemove);
|
|
|
|
|
|
this.paths = paths;
|
|
this.labels = labels;
|
|
this.indicatorLine = canvas.path("M0 " + conf.topInterval
|
|
+ "V" + (conf.height - conf.bottomInterval))
|
|
.attr({stroke: "none", "stroke-width": 1, "stroke-dasharray": "- "});
|
|
this.highlightLine = canvas.path("M0 " + conf.topInterval
|
|
+ "V" + (conf.height - conf.bottomInterval))
|
|
.attr({stroke: "none", "stroke-width": 2});
|
|
|
|
/*
|
|
canvas.setSize(conf.width*2, conf.height);
|
|
canvas.setViewBox(0, 0, conf.width, conf.height/2, false);
|
|
canvas.setSize(conf.width, conf.height);
|
|
*/
|
|
};
|
|
|
|
Stream.prototype.createAxis = function () {
|
|
//all date strings' format are same, string length are same
|
|
var conf = this.defaults,
|
|
date = this.date.slice(this.timeRange[0], this.timeRange[1] + 1),
|
|
left = conf.margin[3],
|
|
//left = conf.margin[3] + conf.legendWidth,
|
|
right = conf.totalWidth - conf.margin[1] - conf.legendWidth,
|
|
tempWord,
|
|
tickNumber,
|
|
getPopPath = function (El) {
|
|
//down pop
|
|
var x = 0,
|
|
y = 0,
|
|
size = 4,
|
|
cw = 23,
|
|
bb = {height: 8};
|
|
if (El) {
|
|
bb = El.getBBox();
|
|
bb.height *= 0.6;
|
|
cw = bb.width / 2 - size;
|
|
}
|
|
return [
|
|
'M', x, y,
|
|
'l', size, size, cw, 0,
|
|
'a', size, size, 0, 0, 1, size, size,
|
|
'l', 0, bb.height,
|
|
'a', size, size, 0, 0, 1, -size, size,
|
|
'l', -(size * 2 + cw * 2), 0,
|
|
'a', size, size, 0, 0, 1, -size, -size,
|
|
'l', 0, -bb.height,
|
|
'a', size, size, 0, 0, 1, size, -size,
|
|
'l', cw, 0,
|
|
'z'
|
|
].join(',');
|
|
};
|
|
|
|
/*
|
|
if (!this.firstRender) {
|
|
return;
|
|
}
|
|
*/
|
|
|
|
this.dateScale = d3.scale.linear()
|
|
.domain([0, date.length - 1])
|
|
.range([left, right]);
|
|
|
|
tempWord = this.axisPaper.text(0, 0, date[0]);
|
|
tickNumber = Math.floor((right - left)
|
|
/ tempWord.getBBox().width / 2) + 1;
|
|
tempWord.remove();
|
|
//tickNumber = 4;
|
|
|
|
Axis().scale(this.dateScale)
|
|
.ticks(tickNumber)
|
|
//.ticks(conf.axisTickNumber)
|
|
.tickSize(6, 3, 3)
|
|
.tickAttr({"stroke": "none"})
|
|
.minorTickAttr({"stroke": "none"})
|
|
.domainAttr({"stroke": "none"})
|
|
//.tickTextAttr({"font-size": conf.fontSize})
|
|
.tickFormat(function (d) {
|
|
return date[d] || "";
|
|
})(this.axisPaper);//.attr({transform: "t0," + (conf.height - 0)});
|
|
|
|
this.axisPopText = this.axisPaper.text(0, 11, date[0])
|
|
.attr({ "text-anchor": "middle",
|
|
"fill": "#fff",
|
|
//"font-size": conf.fontSize,
|
|
"transform": "t" + left + ",0"})
|
|
.hide();
|
|
this.axisPopBubble = this.axisPaper.path(getPopPath(this.axisPopText))
|
|
.attr({ "fill": "#000",
|
|
//"opacity": 0,
|
|
"transform": "t" + left + ",0"})
|
|
.toBack()
|
|
.hide();
|
|
};
|
|
|
|
Stream.prototype.getMaxY = function () {
|
|
return d3.max(this.source, function (d) {
|
|
return d3.max(d, function (d) {
|
|
return d.y0 + d.y;
|
|
});
|
|
});
|
|
};
|
|
|
|
Stream.prototype.mapPathSource = function () {
|
|
var conf = this.defaults,
|
|
maxX = this.source[0].length - 1,//this.digitData[0].length - 1,
|
|
maxY = this.getMaxY(),
|
|
width = conf.width,
|
|
height = conf.height - conf.topInterval - conf.bottomInterval;
|
|
var i, j, l, l2, s, ps;
|
|
this.pathSource = [];
|
|
for (i = 0, l = this.source.length; i < l; i++) {
|
|
this.pathSource[i] = [];
|
|
for (j = 0, l2 = this.source[0].length; j < l2; j++) {
|
|
s = this.source[i][j];
|
|
ps = this.pathSource[i][j] = {};
|
|
ps.x = s.x * width / maxX;
|
|
ps.y0 = height - s.y0 * height / maxY;
|
|
ps.y = s.y * height / maxY;
|
|
}
|
|
}
|
|
};
|
|
|
|
Stream.prototype.generateArea = function () {
|
|
this.mapPathSource();
|
|
var area = d3.svg.area()
|
|
.x(function (d) {
|
|
return d.x;
|
|
})
|
|
.y0(function (d) {
|
|
return d.y0;
|
|
})
|
|
.y1(function (d) {
|
|
return d.y0 - d.y;
|
|
});
|
|
return area;
|
|
};
|
|
|
|
Stream.prototype.generateArea_old = function () {
|
|
var conf = this.defaults,
|
|
maxX = this.digitData[0].length - 1,
|
|
maxY = this.getMaxY(),
|
|
width = conf.width,
|
|
height = conf.height - conf.topInterval - conf.bottomInterval,
|
|
area = d3.svg.area()
|
|
.x(function (d) {
|
|
return d.x * width / maxX;
|
|
})
|
|
.y0(function (d) {
|
|
return height - d.y0 * height / maxY;
|
|
})
|
|
.y1(function (d) {
|
|
return height - (d.y + d.y0) * height / maxY;
|
|
});
|
|
return area;
|
|
};
|
|
|
|
Stream.prototype.clearCanvas = function () {
|
|
this.canvas.clear();
|
|
//this.legend.innerHTML = "";
|
|
/*
|
|
this.legendPaper.clear();
|
|
*/
|
|
this.legend.innerHTML = "";
|
|
this.axisPaper.clear();
|
|
/*
|
|
if (this.firstRender) {
|
|
this.axisPaper = undefined;
|
|
}
|
|
*/
|
|
//this.percentagePaper.clear();
|
|
};
|
|
|
|
Stream.prototype.reRender = function (options) {
|
|
if (!this.node) {
|
|
throw new Error("Please specify which node to render.");
|
|
}
|
|
//this.setOptions(options);
|
|
this.clearCanvas();
|
|
this.layout();
|
|
this.generatePaths();
|
|
this.canAnimate = true;
|
|
};
|
|
|
|
Stream.prototype.render = function (options) {
|
|
if (!this.node) {
|
|
throw new Error("Please specify which node to render.");
|
|
}
|
|
this.firstRender = true;
|
|
//this.setOptions(options);
|
|
this.clearCanvas();
|
|
this.layout();
|
|
this.generatePaths();
|
|
this.firstRender = false;
|
|
this.canAnimate = true;
|
|
};
|
|
|
|
Stream.prototype.resize = function (options) {
|
|
var conf = this.defaults;
|
|
|
|
if (!options.width && !options.height) {
|
|
throw new Error("no width and height input");
|
|
} else if (options.width && !options.height) {
|
|
if (conf.autoHeight) {
|
|
this.setOptions({"width": options.width});
|
|
} else {
|
|
this.setOptions({"width": options.width, "height": conf.height});
|
|
}
|
|
} else if (!options.width && options.height) {
|
|
this.setOptions({"width": conf.totalWidth, "height": options.height});
|
|
} else {
|
|
this.setOptions({"width": options.width, "height": options.height});
|
|
}
|
|
|
|
this.node.innerHTML = "";
|
|
this.createCanvas();
|
|
this.reRender();
|
|
};
|
|
|
|
Stream.prototype.on = function (eventName, callback) {
|
|
if (typeof this.defaults.customEventHandle[eventName] !== 'undefined') {
|
|
this.defaults.customEventHandle[eventName] = callback;
|
|
}
|
|
};
|
|
|
|
Stream.prototype.animate = function (options, timeDuration) {
|
|
//must after render if new Source has been set;
|
|
if (!this.canAnimate) {
|
|
throw new Error("Function animate must be called after render if new Source has been set.");
|
|
}
|
|
var time = 0,
|
|
area,
|
|
color,
|
|
i,
|
|
l;
|
|
if (arguments.length > 1) {
|
|
time = timeDuration;
|
|
}
|
|
|
|
//this.setOptions(options);
|
|
if (options.offset || options.order) {
|
|
this.source = this.remapSource(this.digitData);
|
|
this.layout();
|
|
}
|
|
area = this.generateArea();
|
|
color = this.getColor();
|
|
for (i = 0, l = this.source.length; i < l; i++) {
|
|
var _area = area(this.source[i]);
|
|
var anim = Raphael.animation({path: _area, fill: color(i)}, time);
|
|
this.paths[i].animate(anim);
|
|
}
|
|
};
|
|
|
|
return Stream;
|
|
});
|