@qn-pandora/pandora-visualization
Version:
Pandora 通用可视化库
368 lines (367 loc) • 16.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HoneycombChart = void 0;
var d3 = __importStar(require("d3"));
var d3Hexbin = __importStar(require("d3-hexbin"));
var lodash_1 = require("lodash");
var util_1 = require("./util");
var HoneycombChart = /** @class */ (function () {
function HoneycombChart(dom, addTooltip, removeTooltip, onMouseDown) {
var _this = this;
this._svg = null;
this._bodyG = null;
// option
this._margin = { top: 0, left: 0, right: 0, bottom: 0 };
this.honeycomb = {
maxRadius: 100,
minRadius: 10,
distance: 0.06 // 百分数
};
this.group = {
honeycombNum: 6,
lineGroupNum: 3,
titleHeight: 20,
showTitle: true
};
this.series = [];
this.formatPrecent = function (value, base) {
if (lodash_1.isString(value)) {
return (base * parseInt(value.slice(0, -1))) / 100;
}
return value;
};
this.margin = function () {
var _a = _this._margin, top = _a.top, left = _a.left, right = _a.right, bottom = _a.bottom;
return {
top: _this.formatPrecent(top, _this._height),
left: _this.formatPrecent(left, _this._width),
right: _this.formatPrecent(right, _this._width),
bottom: _this.formatPrecent(bottom, _this._height)
};
};
this.renderSvg = function () {
_this._svg = d3.select(_this._dom).select('svg');
if (_this._svg.empty()) {
_this._svg = d3
.select(_this._dom)
.append('svg')
.attr('width', _this._width)
.attr('height', _this._height);
}
else {
_this._svg.attr('width', _this._width).attr('height', _this._height);
}
_this._svg
.append('defs')
.append('style')
.text('.active-group path{opacity: 0.5;}.active-group .active {opacity: 1;stroke: #000000;stroke-width: 3px;}');
};
this.partX = function () {
var radius = _this.radius();
return (Math.sqrt(3) * radius) / 2;
};
this.getBodyXY = function () {
var _a = _this.margin(), left = _a.left, top = _a.top;
var x = left;
var y = top;
if (_this.series.length === 1) {
var _b = _this.pointLayout(), rows = _b.rows, cols = _b.cols;
var radius = _this.radius();
var partX = _this.partX();
// 一行有多少个蜂窝
var relhoneyNumOnLine = cols;
// const relhoneyNumOnLine =
// count > this.group.honeycombNum ? this.group.honeycombNum : count
var width = partX * (relhoneyNumOnLine * 2 + 1);
x = (_this.quadrantWidth() - width) / 2 + left;
var honeyHeight = 1.88 * radius;
var height = honeyHeight + (honeyHeight / 4) * 3 * (rows - 1);
y = (_this.quadrantHeight() - height) / 2;
}
return { x: x, y: y };
};
this.getRealSvgHeight = function () {
var radius = _this.radius();
var honeyHeight = 1.88 * radius;
var _a = _this.margin(), top = _a.top, bottom = _a.bottom;
var rows = _this.pointLayout().rows;
// 只有一个分组的时候
if (_this.series.length === 1) {
var height = honeyHeight + (honeyHeight / 4) * 3 * (rows - 1);
if (height > _this.quadrantHeight()) {
return top + bottom + height;
}
return _this._height;
}
var newSeries = lodash_1.clone(_this.series);
var result = [];
while (newSeries.length > 0) {
var deleteSeries = newSeries.splice(0, _this.group.lineGroupNum);
var maxPoints = lodash_1.max(deleteSeries.map(function (line) { return line.data.length; }));
var rows_1 = Math.ceil(maxPoints / _this.group.honeycombNum);
var height = honeyHeight + (honeyHeight / 4) * 3 * (rows_1 - 1);
result.push(height + _this.realTitleHeight() + 2);
}
return result.reduce(function (sum, b) {
return sum + b;
}, 0);
};
this.renderBody = function () {
var _a;
var _b = _this.getBodyXY(), x = _b.x, y = _b.y;
if (!_this._bodyG) {
_this._bodyG = _this._svg.append('g')
.attr('class', 'body')
.attr('transform', "translate(" + x + ", " + y + ")")
.attr('clip-path', 'url(#body-clip)');
}
else {
// 更新需要清除旧的图形
_this._bodyG.selectAll('g').remove();
_this._bodyG
.attr('transform', "translate(" + x + ", " + y + ")")
.attr('clip-path', 'url(#body-clip)');
}
if (_this.series.length === 0) {
return;
}
var height = _this.getRealSvgHeight();
if (height > _this._height) {
(_a = _this._svg) === null || _a === void 0 ? void 0 : _a.attr('height', height);
}
_this.renderGroup();
};
// 根据数据获取每个分组宽度和高度
this.groups = function () {
// 分组数目
var groups = _this.series.length;
// 行数
var lines = 1;
if (groups > _this.group.lineGroupNum) {
lines = Math.ceil(groups / _this.group.lineGroupNum);
}
// 计算每一行高度
var lineHeight = lines === 1
? _this.quadrantHeight()
: Math.floor(_this.quadrantHeight() / lines);
// 列数
var columns = groups;
if (lines > 1) {
columns = _this.group.lineGroupNum;
}
// 计算每一组的宽度
var columnWidth = Math.floor(_this.quadrantWidth() / columns);
return { lineHeight: lineHeight, columnWidth: columnWidth, lines: lines, columns: columns };
};
this.pointLayout = function () {
var maxPoints = lodash_1.max(_this.series.map(function (line) { return line.data.length; }));
if (_this.series.length === 1) {
return util_1.getColRows(_this.quadrantWidth(), _this.quadrantHeight(), maxPoints);
}
return {
cols: _this.group.honeycombNum,
rows: Math.ceil(maxPoints / _this.group.honeycombNum)
};
};
// 根据数据获取半径
this.radius = function () {
var _a = _this.groups(), lineHeight = _a.lineHeight, columnWidth = _a.columnWidth;
// 点数量最后的分组对应的点数
var maxPoints = lodash_1.max(_this.series.map(function (line) { return line.data.length; }));
if (!maxPoints) {
return _this.honeycomb.maxRadius;
}
var _b = _this.pointLayout(), rows = _b.rows, cols = _b.cols;
// 根据行高计算六边形高度
var hexgonHeight = Math.floor((lineHeight - _this.realTitleHeight() - 2) / rows);
// 根据列宽计算六边形宽度
var hexgonWidth = Math.floor(columnWidth / (maxPoints > cols ? cols : maxPoints));
// 计算每一个六边形直径
var hexgonDiameter = Math.min(hexgonHeight, hexgonWidth);
var HoneycombRadius = Math.floor(hexgonDiameter / 2 / Math.sign(Math.PI / 3));
return HoneycombRadius > _this.honeycomb.maxRadius
? _this.honeycomb.maxRadius
: HoneycombRadius < _this.honeycomb.minRadius
? _this.honeycomb.minRadius
: HoneycombRadius;
};
// 根据数据及半径求每一行的实际高度
this.lineHeight = function () {
var _a = _this.groups(), lines = _a.lines, columns = _a.columns;
var radius = _this.radius();
return lodash_1.range(lines).map(function (_, line) {
var maxHeight = lodash_1.max(lodash_1.range(columns).map(function (_, column) {
var points = (lodash_1.get(_this.series, [
line * _this.group.lineGroupNum + column,
'data'
]) || []).length;
var pointLines = Math.ceil(points / _this.group.honeycombNum);
return ((radius * 3) / 2) * (pointLines + 0.5);
})) || 0;
return maxHeight + _this.realTitleHeight();
});
};
this.renderGroup = function () {
var _a = _this.groups(), columnWidth = _a.columnWidth, columns = _a.columns;
var lineHeights = _this.lineHeight();
var groupG = _this._bodyG.selectAll('.group')
.data(_this.series)
.enter()
.append('g')
.attr('class', function (_, index) { return "group-" + index; })
.attr('transform', function (_, index) {
var row = Math.floor(index / columns);
var column = index % columns;
return "translate(" + columnWidth * column + ", " + lodash_1.sum(lineHeights.slice(0, row)) + ")";
});
if (_this.group.showTitle) {
groupG
.append('foreignObject')
.attr('x', '0')
.attr('y', '0')
.attr('width', columnWidth + "px")
.attr('height', _this.group.titleHeight + "px")
.attr('transform', function () {
//距离底部有一点的边距
return "translate(0, -20)";
})
.append('xhtml:div')
.text(function (data) {
return "" + (data.name + " (" + data.data.length + ")");
})
.attr('style', 'color: #666;overflow: hidden;text-overflow: ellipsis;white-space:nowrap;');
}
_this.renderHoneycomb(groupG);
};
this.renderHoneycomb = function (groupG) {
var radius = _this.radius();
var hexbin = d3Hexbin.hexbin().radius(radius);
var partX = _this.partX();
var partY = radius * 1.5;
var cols = _this.pointLayout().cols;
groupG.each(function (group, index) {
var data = group.data;
var points = data.map(function (_, index) {
// 计算当前节点处于第几行
var lineNum = Math.floor(index / cols);
// 计算当前节点处于第几列
var columnNum = Math.floor(index % cols);
// 判断当前节点是否为奇数行
var isEventLine = lineNum % 2 === 0;
var x = isEventLine
? partX * (columnNum * 2 + 1)
: partX * (columnNum + 1) * 2;
var y = 1.5 * radius + partY * lineNum;
return [x, y];
});
var that = _this;
d3.select(groupG.nodes()[index])
.selectAll(".path-" + index)
.data(hexbin(points))
.enter()
.append('path')
.attr('class', ".path-" + index)
.attr('transform', function (d) {
// 2, 图形与title 之前有一点的间距
return "translate(" + d.x + ", " + (d.y - radius / 2 + 2) + ")";
})
.attr('fill', function (_, index) { return data[index].color; })
.attr('d', hexbin.hexagon(radius - radius * _this.honeycomb.distance))
// 由于事件通过this传递dom,不能使用箭头函数
.on('mousedown', function (_, index) {
var d = data[index];
that.mousedown(this, d);
})
.on('mousemove', function (_, index) {
var d = data[index];
that.mousemove(this, d);
})
.on('mouseup', function () {
that.mouseup(this);
})
.on('mouseout', function () {
that.mouseout(this);
});
});
};
this.mousedown = function (el, data) {
var _a;
(_a = _this.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(_this, data);
_this.mousemove(el, data);
};
this.mouseup = function (el) {
_this.mouseout(el);
};
this.mousemove = function (el, data) {
d3.select(el).attr('class', 'active');
d3.select(el.parentElement).attr('class', 'active-group');
if (_this.addTooltip) {
_this.addTooltip({ x: d3.event.pageX + 10, y: d3.event.pageY + 10 }, data);
}
};
this.mouseout = function (el) {
d3.select(el).attr('class', '');
d3.select(el.parentElement).attr('class', '');
if (_this.removeTooltip) {
_this.removeTooltip();
}
};
this._dom = dom;
var clientWidth = dom.clientWidth, clientHeight = dom.clientHeight;
this._width = clientWidth;
this._height = clientHeight;
this.addTooltip = addTooltip;
this.removeTooltip = removeTooltip;
this.onMouseDown = onMouseDown;
}
HoneycombChart.prototype.quadrantHeight = function () {
if (this._height === 0)
return 0;
var _a = this.margin(), top = _a.top, bottom = _a.bottom;
return this._height - top - bottom;
};
HoneycombChart.prototype.quadrantWidth = function () {
if (this._width === 0)
return 0;
var _a = this.margin(), left = _a.left, right = _a.right;
return this._width - left - right;
};
HoneycombChart.prototype.realTitleHeight = function () {
return this.group.showTitle ? this.group.titleHeight : 0;
};
HoneycombChart.prototype.render = function (option) {
var margin = option.margin, series = option.series, honeycomb = option.honeycomb, groups = option.groups;
this._margin = margin;
this.honeycomb = honeycomb;
this.group = groups;
this.series = series;
this.renderSvg();
this.renderBody();
};
HoneycombChart.prototype.dispose = function () {
if (this._svg) {
this._svg.remove();
}
};
return HoneycombChart;
}());
exports.HoneycombChart = HoneycombChart;