g6/plugins/tool.mapper/index.js

379 lines
11 KiB
JavaScript
Raw Normal View History

2018-07-10 11:08:57 +08:00
/**
* @fileOverview G6 Mapper Plugin base on d3 tech stack
* d3-scale https://github.com/d3/d3-scale
* d3-legend https://github.com/susielu/d3-legend
* @author shiwu.wyy@antfin.com
*/
const G6 = require('@antv/g6');
const Legend = require('@antv/g2/src/component/legend');
const Util = G6.Util;
const Canvas = require('../../src/extend/g/canvas');
const Scale = require('@antv/scale');
// const d3 = require('d3');
class Plugin {
constructor(options) {
Util.mix(this, {
/**
* 子项类型
* @type {String}
*/
itemType: null,
/**
* 数据纬度
* @type {String}
*/
dim: null,
/**
* 映射域
* @type {Array}
*/
range: null,
/**
* 视觉通道
* @type {String}
*/
channel: null,
/**
* 度量配置
* @type {Object}
*/
scaleCfg: {},
/**
* 图例配置
* @type {Object}
*/
legendCfg: {}
}, options);
}
init() {
const graph = this.get('graph');
graph.on('beforerender', () => {
if (this._checkInput()) {
const legendCfg = this.get('legendCfg');
this._createScale();
this._mapping();
legendCfg && this._createLegend();
graph.on('changesize', () => {
this.updateLegendPosition();
});
}
});
graph.on('afterclear', () => {
const legendCanvas = this.get('legendCanvas');
const filterCallback = this.get('filterCallback');
const graph = this.get('graph');
const itemType = this.get('itemType');
graph['remove' + Util.ucfirst(itemType) + 'Filter'](filterCallback);
legendCanvas.destroy();
});
}
_trainCategoryScale(itemType) {
const dim = this.get('dim');
const graph = this.get('graph');
const itemModels = graph.get(itemType + 's');
const domainMap = {};
const domain = [];
Util.each(itemModels, model => {
if (!domainMap[model[dim]]) {
domainMap[model[dim]] = true;
domain.push(model[dim]);
}
});
return domain;
}
_trainNumberScale(itemType) {
const dim = this.get('dim');
const graph = this.get('graph');
const itemModels = graph.getComputedStyle(itemType + 's');
const domain = [ Infinity, -Infinity ];
Util.each(itemModels, model => {
if (domain[0] > model[dim]) {
domain[0] = model[dim];
}
if (domain[1] < model[dim]) {
domain[1] = model[dim];
}
});
return domain;
}
_getScaleType() {
const itemType = this.get('itemType');
const dim = this.get('dim');
const graph = this.get('graph');
const data = graph.get(itemType + 's');
const scaleCfg = this.get('scaleCfg');
if (!scaleCfg.type) {
if (Util.isNumber(data[0][dim])) {
scaleCfg.type = 'linear';
} else {
scaleCfg.type = 'oridinal';
}
}
return Util.ucfirsts(scaleCfg.type);
}
_createScale() {
const itemType = this.get('itemType');
const scaleCfg = this.get('scaleCfg');
const scaleType = this._getScaleType();
const scale = new Scale['scale' + scaleType]();
const range = this.getComputedStyle('range');
const nice = scaleCfg.nice;
let domain = scaleCfg.domain;
scale.range(range);
if (!domain) {
if (scaleType === 'Ordinal') {
domain = this._trainCategoryScale(itemType, scale);
} else {
domain = this._trainNumberScale(itemType, scale);
}
}
const rangeLength = range.length;
const domainLength = domain.length;
if (rangeLength !== domainLength) {
const domainStep = (domain[1] - domain[0]) / (rangeLength - 1);
Util.each(range, (v, i) => {
domain[i] = domain[0] + i * domainStep;
});
}
if (domain[0] === domain[1]) {
if (domain[0] > 0) {
domain[0] = 0;
} else if (domain[0] < 0) {
domain[1] = 0;
} else {
domain[0] = -1;
}
}
scale.domain(domain);
if (nice !== false && scale.nice) {
scale.nice();
}
Util.isFunction(scaleCfg.callback) && scaleCfg.callback(scale, domain);
this.set('scale', scale);
}
_createLegend() {
const scaleType = this._getScaleType();
const channel = this.get('channel');
const graph = this.get('graph');
const graphContainer = graph.get('graphContainer');
const canvas = new Canvas({
containerDOM: graphContainer,
width: 200,
height: 200
});
let legend;
if (scaleType === 'Ordinal') {
legend = this._createLegend(canvas);
} else {
if (channel === 'color') {
legend = this._createContinuousColorLegend(canvas);
} else {
legend = this._createContinuousSizeLegend(canvas);
}
}
const bbox = legend.getBBox();
const padding = 6;
const legendWidth = bbox.maxX - bbox.minX;
const legendHeight = bbox.maxY - bbox.minY;
legend.move(-bbox.minX + padding, -bbox.minY + padding);
canvas.changeSize(legendWidth + 2 * padding, legendHeight + 2 * padding);
this.set('legend', legend);
this.set('legendCanvas', canvas);
this.set('legendWidth', legendWidth);
this.set('legendHeight', legendHeight);
canvas.draw();
}
updateLegendPosition() {
const legend = this.get('legend');
if (!legend) {
return;
}
const canvas = this.get('legendCanvas');
const legendCfg = this.getComputedStyle('legendCfg');
const marginTop = legendCfg.marginTop ? legendCfg.marginTop : 0;
const marginLeft = legendCfg.marginLeft ? legendCfg.marginLeft : 0;
const marginBottom = legendCfg.marginBottom ? legendCfg.marginBottom : 0;
const marginRight = legendCfg.marginRight ? legendCfg.marginRight : 0;
const position = legendCfg.position ? legendCfg.position : 'br';
const graph = this.get('graph');
const graphWidth = graph.get('width');
const graphHeight = graph.get('height');
const el = canvas.get('el');
const legendWidth = this.get('legendWidth');
const legendHeight = this.get('legendHeight');
const tl = Util.getNineBoxPosition(position, {
x: 0,
y: 0,
width: graphWidth,
height: graphHeight
}, legendWidth, legendHeight, [ marginTop, marginRight, marginBottom, marginLeft ]);
el.style.position = 'absolute';
el.style.top = tl.y + 'px';
el.style.left = tl.x + 'px';
}
_createCatLegend(canvas) {
const scale = this.get('scale');
const range = scale.range();
const domain = scale.domain();
const itemType = this.get('itemType');
const legendCfg = this.get('legendCfg');
const items = [];
const cfg = Util.mix({
items,
checkable: false
}, legendCfg);
Util.each(range, (value, i) => {
items.push({
name: domain[i],
color: value,
type: itemType === 'node' ? 'circle' : 'line',
layout: 'vertical',
marker: {
symbol: 'circle',
radius: 5,
fill: value
},
checked: true
});
});
const legend = canvas.addGroup(Legend.Category, cfg);
return legend;
}
_createContinuousColorLegend(canvas) {
const itemType = this.get('itemType');
const scale = this.get('scale');
const range = scale.range();
const domain = scale.domain();
const domainStep = (domain[domain.length - 1] - domain[0]) / (range.length - 1);
const legendCfg = this.get('legendCfg');
const items = [];
const cfg = Util.mix({
items,
theme: 'gradient',
attrType: 'color',
titleText: itemType,
title: {
fill: '#333',
textBaseline: 'bottom'
},
width: 15,
height: 150
}, legendCfg);
Util.each(range, (value, i) => {
const name = domain[0] + domainStep * i;
items.push({
name,
value: i / (range.length - 1),
color: value
});
});
const legend = canvas.addGroup(Legend.Continuous, cfg);
this.reBindLegendUI(legend, scale);
return legend;
}
_createContinuousSizeLegend(canvas) {
const itemType = this.get('itemType');
const scale = this.get('scale');
const range = scale.range();
const domain = scale.domain();
const domainStep = (domain[domain.length - 1] - domain[0]) / (range.length - 1);
const legendCfg = this.get('legendCfg');
const items = [];
Util.each(range, (value, i) => {
const name = domain[0] + domainStep * i;
items.push({
name,
value: i / (range.length - 1)
});
});
const cfg = Util.mix({
items,
attrType: 'size',
titleText: itemType,
title: {
fill: '#333',
textBaseline: 'bottom'
},
width: 15,
height: 150
}, legendCfg);
const legend = canvas.addGroup(Legend.Continuous, cfg);
this.reBindLegendUI(legend, scale);
return legend;
}
_mapping() {
const graph = this.get('graph');
const dim = this.get('dim');
const itemType = this.get('itemType');
const scale = this.get('scale');
const channel = this.get('channel');
graph[itemType]()[channel](model => {
if (itemType === 'node' && channel === 'size') {
return scale(model[dim]) * 2;
}
return scale(model[dim]);
});
}
reBindLegendUI(legend, scale) {
const graph = this.get('graph');
const dim = this.get('dim');
const itemType = this.get('itemType');
const domain = scale.domain();
const domainMin = domain[0];
const domainMax = domain[domain.length - 1];
const domainFilter = [ domainMin, domainMax ];
const rangeElement = legend.get('rangeElemeng');
const legendCfg = this.get('legendCfg');
const callback = model => {
return model[dim] >= domainFilter[0] && model[dim] <= domainFilter[1];
};
const trigerWidth = legendCfg.trigerWidth ? legendCfg.trigerWidth : 16;
const trigerMarginLeft = legendCfg.trigerMarginLeft ? legendCfg.trigerMarginLeft : trigerWidth / 2;
legend.get('minTextElement').translate(trigerMarginLeft, -(16 - trigerWidth) / 2);
legend.get('maxTextElement').translate(trigerMarginLeft, +(16 - trigerWidth) / 2);
graph['add' + Util.ucfirst(itemType) + 'Filter'](callback);
rangeElement.on('rangeChange', ev => {
domainFilter[0] = domainMin + (domainMax - domainMin) * (ev.range[0] / 100);
domainFilter[1] = domainMin + (domainMax - domainMin) * (ev.range[1] / 100);
graph.BiquadFilterNode(itemType);
});
legend._updateElement = function(min, max) {
min = Number(min);
max = Number(max);
const minTextElement = legend.get('minTextElement');
const maxTextElement = legend.get('maxTextElement');
const minText = legendCfg.formatter ? legendCfg.formatter(min) : min;
const maxText = legendCfg.formatter ? legendCfg.formmater(max) : max;
minTextElement.attr('text', minText + '');
maxTextElement.attr('text', maxText + '');
if (legend.get('attrType') === 'color') {
const minButtonElement = legend.get('minButtonElement');
const maxButtonElement = legend.get('maxButtonElement');
minButtonElement.attr('fill', scale(min));
maxButtonElement.attr('fill', scale(max));
}
};
legend._updateElement(domainMin, domainMax);
this.set('filterCallback', callback);
}
_checkInput() {
const itemType = this.get('itemType');
const graph = this.get('graph');
const itemModels = graph.get(itemType + 's');
return graph && itemModels && itemModels.length > 0;
}
}
G6.Plugins['tool.mapper'] = Plugin;
module.exports = Plugin;