refine bar chart

This commit is contained in:
gozo1234 2012-11-30 15:13:38 +08:00
parent c6c1f52bfc
commit 6299b1753e
2 changed files with 181 additions and 169 deletions

View File

@ -50,9 +50,10 @@
var bar = new Bar("chart", {
"width": 980,
"height": 600,
"margin": 50,
"margin": [10, 20, 30, 50],
"gap": 20,
"yBase": 50
"yBase": 50,
"showLegend": true
});
bar.setSource(source, {
bar: 0,

View File

@ -1,6 +1,6 @@
/*global Raphael, d3, $, define, _ */
/*!
* Column图的兼容性定义
* Bar图的兼容性定义
*/
;(function (name, definition) {
if (typeof define === 'function') { // Module
@ -12,73 +12,77 @@
var DataV = require('DataV');
/**
* Column构造函数
* Bar构造函数
* Creates Bar in a DOM node with id "chart", default width is 522; height is 522px;
* Options:
*
* - `width` 宽度默认为节点宽度
* - `xBase` 横坐标的基线值有的以0为起始值有的则以数据中的最小值为起始值
* - `yBase` 横坐标的基线值有的以0为起始值有的则以数据中的最小值为起始值
* - `gap` 组与组之间的缝隙宽度
*
* Examples:
* ```
* var bar = new Bar("chart", {"width": 500, "height": 600, "typeNames": ["Y", "Z"]});
* var bar = new Bar("chart", {"width": 500, "height": 600);
* ```
* @param {Mix} node The dom node or dom node Id
* @param {Object} options options json object for determin bar style.
*/
var Bar = DataV.extend(DataV.Chart, {
initialize: function (node, options) {
this.type = "Bar";
this.node = this.checkContainer(node);
this.type = "Bar";
this.node = this.checkContainer(node);
/**
* 柱纬度
*/
this.dimension.bar = {
type: "string",
required: true,
index: 0
};
/**
* 横向纬度
*/
this.dimension.x = {
type: "string",
required: true,
index: 1
};
/**
* 值纬度
*/
this.dimension.value = {
type: "number",
required: true,
index: 2
};
/**
* 柱纬度
*/
this.dimension.bar = {
type: "string",
required: true,
index: 0
};
/**
* 横向纬度
*/
this.dimension.x = {
type: "string",
required: true,
index: 1
};
/**
* 值纬度
*/
this.dimension.value = {
type: "number",
required: true,
index: 2
};
this.defaults.typeNames = [];
// canvas parameters
this.defaults.width = 522;
this.defaults.height = 522;
this.defaults.margin = 50;
this.defaults.gap = 15;
this.defaults.circleR = 3;
this.defaults.barColor = ["#308BE6","#8EEC00","#DDDF0D"];
this.defaults.xTickNumber = 5;
this.defaults.yTickNumber = 5;
this.defaults.xBase = undefined;
//图例区域的左上顶点坐标xy
this.defaults.legendArea = [422, 50, 472, 220];
//散点矩阵区域的左上顶点坐标xy
this.defaults.diagramArea = [50, 50, 422, 472];
this.barSet = [];
this.setOptions(options);
this.createCanvas();
this.initEvents();
this.defaults.typeNames = [];
// canvas parameters
this.defaults.width = 522;
this.defaults.height = 522;
this.defaults.margin = [10, 20, 30, 50];
this.defaults.gap = 15;
this.defaults.circleR = 3;
this.defaults.barColor = ["#308BE6","#8EEC00","#DDDF0D"];
this.defaults.xTickNumber = 5;
this.defaults.yTickNumber = 5;
this.defaults.legendWidth = 100;
this.defaults.yBase = 0;
this.defaults.showLegend = true;
this.barSet = [];
this.formatLabel = function (text) {
return text;
};
this.formatXScale = function (text) {
return text;
};
this.formatValue = function (value) {
return value;
};
this.setOptions(options);
this.createCanvas();
this.initEvents();
}
});
@ -134,7 +138,7 @@
* ```
* bar.setSource(source);
* ```
* @param {Array} source 数据源 第一列为排布在x轴的数据后n列为排布在y轴的数据
* @param {Array} source 数据源 第一列为排布在y轴的数据后n列为排布在x轴的数据
*/
Bar.prototype.setSource = function (source, map) {
var conf = this.defaults;
@ -166,16 +170,20 @@
*/
Bar.prototype.setAxis = function () {
var conf = this.defaults;
if (conf.showLegend) {
conf.legendArea = [conf.width - conf.legendWidth, 0, conf.width, conf.height];
} else {
conf.legendWidth = 0;
conf.legendArea = [0, 0, 0, 0];
}
var tagWidth = conf.width / 5 > 50 ? 50 : conf.width / 5;
conf.legendArea = [conf.width - tagWidth - conf.margin, 0, conf.width, conf.height];
conf.diagramArea = [0, 0, conf.width - tagWidth - conf.margin, conf.height];
var w = conf.diagramArea[2] - 2 * conf.margin;
var h = conf.diagramArea[3] - conf.margin;
var margin = conf.margin;
conf.diagramArea = [margin[3], margin[0], conf.width - conf.legendWidth - margin[1], conf.height - margin[2]];
//设置x轴
this.value = d3.scale.linear().domain(conf.xExtent).range([conf.margin, w]);
this.value = d3.scale.linear().domain(conf.xExtent).range([conf.diagramArea[0], conf.diagramArea[2]]);
//设置y轴
this.y = d3.scale.linear().domain([0, conf.yAxisData.length]).range([h, conf.margin]);
this.y = d3.scale.linear().domain([0, conf.yAxisData.length]).range([conf.diagramArea[3], conf.diagramArea[1]]);
var valueRange = this.value.range();
var yRange = this.y.range();
var axis = this.axisPosition = {
@ -204,9 +212,10 @@
var axis = this.axisPosition;
var ticks;
// X轴
var formatXScale = conf.formatXScale || that.formatXScale;
ticks = this.value.ticks(conf.xTickNumber);
for (j = 0; j < ticks.length; j++) {
tickText.push(paper.text(this.value(ticks[j]), axis.down + 14, ticks[j]).attr({
tickText.push(paper.text(this.value(ticks[j]), axis.down + 14, formatXScale(ticks[j])).attr({
"fill": "#878791",
"fill-opacity": 0.7,
"font-size": 12,
@ -235,13 +244,13 @@
ticks = this.y.ticks(conf.yTickNumber);
console.log(ticks);
var range = this.y.range();
var formatLabel = conf.formatLabel || that.formatLabel;
// 修复显示不从第一个x轴单位显示的bug
for (j = 0; j < ticks.length; j++) {
// 修改x轴单位显示在所有Column组的中间位置
// 修复x轴单位对于柱位置的偏移
var y = this.y(ticks[j]) - conf.gap / 2 - this.barCount * Math.floor(this.barWidth) / 2;
tickText.push(paper.text(axis.left - 10, y, conf.yAxisData[ticks[j]]).rotate(45, axis.left - 10, y).attr({
tickText.push(paper.text(axis.left - 10, y, formatLabel(conf.yAxisData[ticks[j]])).rotate(45, axis.left - 10, y).attr({
"fill": "#878791",
"fill-opacity": 0.7,
"font-size": 12,
@ -249,8 +258,6 @@
}));
axisLines.push(paper.path("M" + axis.left + "," + y + "L" + (axis.left - 5) + "," + y));
}
tickText;
axisLines.push(paper.path("M" + axis.left + "," + axis.down + "L" + axis.right + "," + axis.down));
axisLines.attr({
"stroke": "#D7D7D7",
@ -276,113 +283,114 @@
//bars
var mouseOverBar = function (event) {
var barIndex = this.data('bar');
var yIndex = this.data('index');
if (that.clicked && that.clickedBarIndex !== barIndex) {
return;
}
tagSet.remove();
var currentSet = barSet.filter(function (set, barIndex) {
return that.clicked ? that.clickedBarIndex === barIndex : true;
});
currentSet.forEach(function (set, barIndex) {
set.animate({
"fill-opacity": 0.3
}, 10);
set[yIndex].animate({
"fill-opacity":1
}, 10);
});
var hovered = currentSet.map(function (set) {
return set[yIndex];
});
var yPos = _.max(hovered, function (item) {
return item.attrs.y;
}).attrs.y + barWidth + 8;
var x = _.map(hovered, function (item) {
return item.attrs.x + item.attrs.width;
});
// TODO: 防遮罩算法
for (var i = 1; i < x.length; i++) {
for (var j = i - 1; j >= 0; j--) {
var overlapped = x.filter(function (item, index) {
return index < i && Math.abs(item - x[i]) < 45;
});
if (overlapped.length > 0) {
var extent = d3.extent(overlapped);
if (x[i] <= extent[0]) {
x[i] = extent[0] - 45;
} else {
x[i] = extent[1] + 45;
}
}
var barIndex = this.data('bar');
var yIndex = this.data('index');
if (that.clicked && that.clickedBarIndex !== barIndex) {
return;
}
}
hovered.forEach(function (item, barIndex) {
var xPos = x[barIndex];
var valueLabel = '' + values[barIndex][yIndex][dim.value.index];
var textWidth = 5 * valueLabel.length + 20;
tagSet.remove();
var currentSet = barSet.filter(function (set, barIndex) {
return that.clicked ? that.clickedBarIndex === barIndex : true;
});
currentSet.forEach(function (set, barIndex) {
set.forEach(function (bar, index) {
if(yIndex == index) {
bar.stop().attr({
"fill-opacity": 1
});
} else {
bar.stop().animate({
"fill-opacity": 0.3
},100);
}
});
});
var rect = paper.rect(xPos - textWidth / 2, yPos - 5, textWidth, 20, 2).attr({
"fill": conf.barColor[barIndex],
"fill-opacity": 1,
"stroke": "none"
var hovered = currentSet.map(function (set) {
return set[yIndex];
});
var path = paper.path("M" + (xPos - 4) + "," + (yPos - 5) + "L" + xPos + "," + yPos + "L" + (xPos +4) + "," + (yPos - 5) + "H" + (xPos - 4) + "Z").attr({
"fill" : conf.barColor[barIndex],
"stroke" : conf.barColor[barIndex]
});
var text = paper.text(xPos, yPos + 5, valueLabel).attr({
"fill": "#ffffff",
"fill-opacity": 1,
"font-weight": "bold",
"font-size": 12,
"text-anchor": "middle"
});
tagSet.push(rect, path, text);
});
var yPos = _.max(hovered, function (item) {
return item.attrs.y;
}).attrs.y + barWidth + 8;
xPos = hovered.reduce(function (pre, cur) {
return pre + cur.attrs.x;
}, 0) / hovered.length + barWidth / 2;
var xLabel = '' + values[barIndex][xIndex][dim.x.index];
var textWidth = 6 * xLabel.length + 20;
//axis x rect
var rect = paper.rect(xPos - textWidth / 2, axis.down + 8, textWidth, 20, 2).attr({
"fill": "#5f5f5f",
"fill-opacity": 1,
"stroke": "none"
});
// axis x text
var text = paper.text(xPos, axis.down + 18, xLabel).attr({
"fill": "#ffffff",
"fill-opacity": 1,
"font-weight": "bold",
"font-size": 12,
"text-anchor": "middle"
});
var arrow = paper.path("M" + (xPos - 4) + "," + (axis.down + 8) + "L" + xPos + "," + axis.down +
"L" + (xPos + 4) + "," + (axis.down + 8) + "H" + xPos + "Z").attr({
"fill": "#5F5F5F",
"stroke": "#5F5F5F"
});
tagSet.push(rect, text, arrow);
var x = _.map(hovered, function (item) {
return item.attrs.x + item.attrs.width;
});
// TODO: 防遮罩算法
for (var i = 1; i < x.length; i++) {
for (var j = i - 1; j >= 0; j--) {
var overlapped = x.filter(function (item, index) {
return index < i && Math.abs(item - x[i]) < 45;
});
if (overlapped.length > 0) {
var extent = d3.extent(overlapped);
if (x[i] <= extent[0]) {
x[i] = extent[0] - 45;
} else {
x[i] = extent[1] + 45;
}
}
}
}
hovered.forEach(function (item, barIndex) {
var xPos = x[barIndex];
var formatValue = conf.formatValue || that.formatValue;
var valueLabel = formatValue('' + values[barIndex][yIndex][dim.value.index]);
var textWidth = 5 * valueLabel.length + 20;
var rect = paper.rect(xPos - textWidth / 2, yPos, textWidth, 20, 2).attr({
"fill": conf.barColor[barIndex],
"fill-opacity": 1,
"stroke": "none"
});
var path = paper.path("M" + (xPos - 4) + "," + yPos + "L" + xPos + "," + (yPos - 5) + "L" + (xPos +4) + "," + yPos + "H" + (xPos - 4) + "Z").attr({
"fill" : conf.barColor[barIndex],
"stroke" : conf.barColor[barIndex]
});
var text = paper.text(xPos, yPos + 10, valueLabel).attr({
"fill": "#ffffff",
"fill-opacity": 1,
"font-weight": "bold",
"font-size": 12,
"text-anchor": "middle"
});
tagSet.push(rect, path, text);
});
yPos = hovered.reduce(function (pre, cur) {
return pre + cur.attrs.y;
}, 0) / hovered.length + barWidth / 2;
var formatLabel = conf.formatLabel || that.formatLabel;
var yLabel = formatLabel('' + values[barIndex][yIndex][dim.x.index]);
var textWidth = 6 * yLabel.length + 20;
//axis y rect
var rect = paper.rect(axis.left - textWidth, yPos - barWidth + 2, textWidth, 20, 2).attr({
"fill": "#5f5f5f",
"fill-opacity": 1,
"stroke": "none"
}).rotate(45, axis.left, yPos - barWidth + 2, textWidth);
// axis y text
var text = paper.text(axis.left - 2, yPos + 2, yLabel).attr({
"fill": "#ffffff",
"fill-opacity": 1,
"font-weight": "bold",
"font-size": 12,
"text-anchor": "end"
}).rotate(45, axis.left - 2, yPos - barWidth + 2, textWidth);
tagSet.push(rect, text);
};
var mouseOutBar = function (event) {
var barIndex = this.data('bar');
var xIndex = this.data('index');
var currentSet = barSet.filter(function (set, barIndex) {
return that.clicked ? that.clickedColumnIndex === barIndex : true;
});
tagSet.animate({"opacity": 0}, 1000, function () {
tagSet.remove();
});
currentSet.forEach(function (set, barIndex) {
set.attr({"fill-opacity": 1});
});
var barIndex = this.data('bar');
var yIndex = this.data('index');
var currentSet = barSet.filter(function (set, barIndex) {
return that.clicked ? that.clickedBarIndex === barIndex : true;
});
tagSet.stop().animate({"opacity": 0}, 300, function () {
tagSet.remove();
});
currentSet.forEach(function (set) {
set.stop().animate({"fill-opacity": 1}, 100);
});
};
values.forEach(function (bar, index) {
@ -391,14 +399,14 @@
var value = row[dim.value.index];
var height = that.value(value);
var y = that.y(i);
var rect = paper.rect(axis.left, y - barWidth * (index + 1) - conf.gap / 2, height, barWidth).attr({
var rect = paper.rect(axis.left, y - barWidth * (index + 1) - conf.gap / 2, height - axis.left, barWidth).attr({
"fill": conf.barColor[index],
"fill-opacity": 1,
"stroke": "none"
});
rect.data('bar', index).data('index', i);
rect.mouseover(mouseOverBar);
rect.mouseout(mouseOutBar);
rect.mouseover(_.debounce(mouseOverBar, 300));
rect.mouseout(_.debounce(mouseOutBar, 300));
barSet[index].push(rect);
});
});
@ -415,6 +423,9 @@
var conf = this.defaults;
var legendArea = conf.legendArea;
var barCount = this.barCount;
if(!conf.showLegend) {
return;
}
//legend
var mouseOverLegend = function (event) {
if (legendSet.clicked) {