D3的使用(Flow事件IP(PORT)关系可视化实现)

图片:
[align=center]

[img]http://dl2.iteye.com/upload/attachment/0101/9901/1b9f8caa-f2ca-3e5e-b834-8080e274f454.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0101/9911/759054ed-b046-3aaf-a85b-ada017a3404f.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0101/9903/50a073a0-1f35-32a4-8c64-d23567a6e07b.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0101/9905/56380f30-8792-37b1-8565-ed7c2ba4d843.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0101/9907/f679d067-7377-3245-b458-056756e833b6.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0101/9909/3d72debd-a16e-3384-bc29-9cb4e0ab0351.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0101/9913/77404938-4b44-34fe-a4c7-550f8415f44c.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0101/9915/b713dcd4-ec98-3c89-97b7-a7e8d1f275ac.png[/img]

[img]http://dl2.iteye.com/upload/attachment/0101/9917/7441acb6-55e7-366a-bed5-b442c56be41f.png[/img]

[/align]
[align=left]

/**
* 基于D3关系分布图
* @author wang xiu fu
*/
Ext.define('shyl.view.flowevent.IPPortRelationView', function() {
var Aesset = {
notEmpty: function(o, message) {
if (o === null || o === undefined || (Ext.isArray(o) && o.length === 0) || (Ext.isString(o) && o === '')) {
alert(message);
Ext.Error.raise(message);
}
}
},
xyRe = /\(((-)*([(\d)\.]+)){1}(?:,)((-)*([(\d)\.]+)){1}\)/g,
getXYFromTranslate = function(translate) {
if (!translate) {
return;
}
var res, fn = function(m1,m2, m3, m4, m5, m6, m7) {res = [parseInt(m2), parseInt(m5)];};
translate = translate.replace(/\s/g, '');
translate.replace(xyRe, fn);
return res;
},
replaceStrTemplate = function(str, v) {
for (var pro in v) {
if (v.hasOwnProperty(pro)) {
str = str.replace('{'+ pro +'}', v[pro]);
}
}
return str;
},
toFix = function(d, n) {
var e = Math.pow(10, n);
return Math.floor(d * e) / e;
},
delayFn = function(fn, scope, timeout, args) {
if (fn.delayTimeoutId) {
clearTimeout(fn.delayTimeoutId);
}
fn.delayTimeoutId = setTimeout(function() {
if (args) {
fn.apply(scope, args);
return;
}
fn.call(scope);
}, timeout)
},
calculateAxisLabelFn = function(v) { //提供一个简单的文本实际宽度计算函数
v = String(v);
var dotcount = v.split('.').length - 1;
return (v.length - dotcount) * 7 + dotcount*4;
},
currentSelectScale, currentSelectLine;

return {
extend: 'Ext.Component',
alias: 'widget.ipportrelationview',
surfaceCls: 'surface-cls',
titleCls: 'surface-title-cls',
surfaceAxisLeftCls: 'surface-axis-left-Cls',
surfaceAxisLeftLabelCls: 'surface-axis-left-label-Cls',
surfaceAxisRightCls: 'surface-axis-right-Cls',
surfaceAxisRightLabelCls: 'surface-axis-right-label-Cls',
lineItemCls: 'line-item-cls',//线的默认样式
emptyTextCls: 'empty-text-cls',
displayLegendCls: 'display-legend-cls',
/**
* axis配置:
* {
* whenLeft: function(index, total , value, fieldName, data, dataIndex) {} //用户分组
* vField:
* rField:
* leftTitle:
* rightTitle:
* }
*/
axis: null,
//data config
store: null, //store
legend: null, // { type: '' }如果为false,则不显示legend信息,
emptyText: '未加载到数据',

//loadMsg
loadingText: '加载中....',

initComponent: function() {
var me = this,
bind = Ext.bind,
tipButtonSettings = this.tipButtonSettings || {},
loadMsgCfg;
loadMsgCfg = {
target: this,
msg: me.loadingText,
msgCls: me.loadingCls,
useMsg: true,
store: this.store
};
me.callParent(arguments);

me.loadMask = new Ext.LoadMask(loadMsgCfg);

//header高度
this.headerHeight = this.headerHeight || 80;
//setup axis
this.setupAxis();

tipButtonSettings.settings = tipButtonSettings.settings || {};
tipButtonSettings.settings.data = [1, 2, 3];
tipButtonSettings.settings.colors = ['#E0EEE0', '#CD8162', '#FF0000'];
tipButtonSettings.settings.expression = [{lessthan: [10000000, 0.0004]}, {lessthan: [50000000, 0.002]}];
this.tipButtonSettings = tipButtonSettings;
me.mon(this.store, {
refresh: bind(me.onStoreLoad, me)
});

this.addEvents({
onLinesClick: true
})
},

setupAxis: function() {
var me = this,
axis = this.axis || {};
if (!axis.whenLeft || !Ext.isFunction(axis.whenLeft)) {
axis.whenLeft = function(index, total , value, fieldName, data, dataIndex) {
return index % 2 === 0;
}
}
Aesset.notEmpty(axis.vField, 'not define "vField" of axis');
Aesset.notEmpty(axis.rField, 'not define "rField" of axis');

axis.leftTitle = axis.leftTitle || '左';
axis.rightTitle = axis.rightTitle || '右';
axis.data = {leftTotal: 0, rightTotal: 0, total: 0, left: [], right: [], data: []};
axis.leftSpace = axis.leftSpace || 100;
axis.leftRightSpace = axis.leftRightSpace || 300;
axis.leftSpritColor = axis.leftSpritColor || '#f0a50a';
axis.rightSpritColor = axis.rightSpritColor || '#c20024';
this.axisLableSpace = this.axisLableSpace || 30;
},
onScrollEvent: function() {
delayFn(this.updateTipButtonPosition, this, 50);
delayFn(this.showPortRelationSprite, this, 50);
} ,
afterRender: function() {
var me = this;
me.callParent(arguments);
//监听scroll事件
this.getEl().addListener('scroll', me.onScrollEvent, me);
},

onStoreLoad: function(store, eOpts) {
this.hidePortRelationSprite();
//清理缓存数据
this.axis.data = {leftTotal: 0, rightTotal: 0, total: 0, left: [], right: [], data: []};
var datas = store.data;
//格式化数据
this.formatDatas(datas);
//创建Surface
this.createSurface();
if (!datas|| datas.length === 0) {
this.showEmptyText();
} else {
this.hideEmptyText();
}
this.loadMask.show();
this.loadMask.msgTextEl.update('渲染中...');
//格式化数据
this.onRefresh();
},
showEmptyText: function() {
var surface = this.surface,
bbox, centerX, emptyTextSpiteWidth;
if (!this.emptyTextSpite) {
this.emptyTextSpite = surface.append('svg:g');
this.emptyTextSpite.append('svg:text')
.text(this.emptyText)
.style('font-size', '12px')
.style('font-family', 'Arial')
.style('fill', '#FFF')
.attr('class', this.emptyTextCls);
}
this.createSurface();
bbox = this.getBBox();
centerX = bbox.width/2;
//获取emptyTextSpite的宽度
emptyTextSpiteWidth = this.emptyTextSpite.select('text')[0][0].offsetWidth;
this.emptyTextSpite.attr('transform', 'translate(' + (centerX-(emptyTextSpiteWidth/2)) + ', ' + 100 + ')').style('display', '');
},
hideEmptyText: function() {
if (this.emptyTextSpite) {
this.emptyTextSpite.style('display', 'none');
}
},

formatDatas: function(datas) {
var me = this,
axis = this.axis,
whenLeft = axis.whenLeft,
axisDatas = axis.data, //axis.data = {leftTotal: 0, rightTotal: 0, total: 0, left: [], right: [], data: []};
vfield = axis.vField,
rfield = axis.rField,
i, len, record, count = 0, leftcount = 0, rightcount = 0, tempDataCache = {};
if (this.drawStatus == 1) {
axisDatas.leftTotal = 0;
axisDatas.rightTotal = 0;
axisDatas.left = [];
axisDatas.right = [];
axisDatas.data = [];
}
for (i=0, len = datas.length; i<len; i++) {
record = datas.get(i);
if (!tempDataCache[record.get(vfield)]) {
axisDatas.data.push({name: record.get(vfield), field: vfield, data: record, dataIndex: i});
tempDataCache[record.get(vfield)] = true;
count++;
}
if (!tempDataCache[record.get(rfield)]) {
axisDatas.data.push({name: record.get(rfield), field: rfield, data: record, dataIndex: i});
tempDataCache[record.get(rfield)] = true;
count++;
}
}
axisDatas.total = count; //总个数
for (i=0; i<count; i++) {
record = axisDatas.data[i];
if (whenLeft.call(this, i, count , record.name, record.field, record.data, record.dataIndex)) {
record.position = 1;
axisDatas.left.push(record);
leftcount++;
} else {
record.position = 2;
axisDatas.right.push(record);
rightcount++;
}
}
axisDatas.leftTotal = leftcount;
axisDatas.rightTotal = rightcount;
},

/**
* 创建SVG DOM
*/
createSurface: function() {
var me = this,
axis = this.axis,
axisLableSpace = axis.axisLableSpace || 25,
headerHeight = this.headerHeight || 80,
el = me.getEl(),
bbox = this.getBBox(),
floor = Math.floor,
axis = this.axis,
axisDatas = axis.data, //axis.data = {leftTotal: 0, rightTotal: 0, total: 0, left: [], right: [], data: []};
bodyDom = el.dom,
max = Math.max,
surface, maxcount, realHeight, necessaryHeight, axisLableTopY;
realHeight = bbox.height - 2 - headerHeight;
maxcount = max(axisDatas.leftTotal, axisDatas.rightTotal);
//计算svg的总高度
necessaryHeight = maxcount * axisLableSpace;
this.axisLableTopY = headerHeight;
if (necessaryHeight < realHeight) {
axisLableTopY = this.axisLableTopY = headerHeight + floor((realHeight - necessaryHeight) / 2);
}
realHeight = necessaryHeight + headerHeight;
if (!this.surface) {
surface = d3.select(bodyDom).append('svg')
.attr('class', this.surfaceCls)
me.surface = surface;
}
axis.axisLableSpace = axisLableSpace;
this.headerHeight = headerHeight;

this.surface.svgHeight = (realHeight < (bbox.height - 2) ? (bbox.height - 2) : realHeight);
me.autoSizeSurface(this.surface.svgHeight);
},

/**
* 获取绘制环境容器的size信息
*/
getBBox: function() {
var el = this.getEl(),
innerSpace = this.innerSpace || 5;
this.innerSpace = innerSpace;
return {
x: innerSpace,
y: innerSpace,
width: el.getWidth(),
height: el.getHeight(),
scrollTop: el.getScrollTop()
}
},

/**
* 动态设置当前svg dom 的width 、height
*/
autoSizeSurface: function(height, width) {
var me = this,
box = me.getBBox();
height || (height = box.height);
width || (width = box.width);
me.surface.attr('width', width-25)
.attr('height', height);
},

onRefresh: function() {
this.setTitle(this.title);
//this.showTipButton();
//this.hideTipButton();
this.redraw();
this.loadMask.hide();
},

redraw: function() {
this.drawAxis();
this.drawLines();
this.drawLegend();
this.drawStatus = 1;
},

setTitle: function(title) {
var titleSprite = this.titleSprite;
if (!title) {
return;
}
if (!titleSprite) {
titleSprite = this.titleSprite = this.surface.append('svg:g');
titleSprite.append('svg:text')
.text('')
.attr('class', this.titleCls)
.style('font-size', '14px')
.style('font-weight', 'bold')
.style('font-family', 'Arial');
}
titleSprite.attr('transform', 'translate(10, 20)');
d3.select(titleSprite.selectAll('text')[0][0]).text(title || '');
},
//绘制图例用于显示明细信息
drawLegend: function() {
if (!this.legendSprite) {
this.legendSprite = this.surface.append('svg:g').style('display', 'none');
this.legendSprite.append('svg:rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', 100)
.attr('height', 20)
.attr('rx', 0)
.attr('ry', 0)
.attr('fill', '#EEE9E9')
.attr('stroke-width', 0.5)
.attr('stroke', '#000');
}
},
showLegend: function(pos) {
var axis = this.axis,
legend = this.legend,
minLegendWidth = legend.minWidth || 100,
displayLabels = legend.displayLabels,
svgHeight = this.surface.svgHeight,
displayLegendCls = this.displayLegendCls,
allTexts, maxWidth = minLegendWidth,
rightx = axis.rightx,
max = Math.max,
i, len, legendHeight, legendSpriteDom;
legend.minWidth = minLegendWidth;
legendSpriteDom = this.legendSprite[0][0];
//解决覆盖问题
this.legendSprite.remove();
this.surface[0][0].appendChild(legendSpriteDom);
//获取legend的宽度
allTexts = this.legendSprite.selectAll('text.' + displayLegendCls);
for (i=0, len=allTexts.length; i<len; i++) {
maxWidth = max(maxWidth, allTexts[i][0].offsetWidth);
}
maxWidth = maxWidth + 40;
this.legendSprite.style('display', '');
if (pos[0] > (rightx - 50)) {
pos[0] = pos[0] - maxWidth;
}
legendHeight = (displayLabels.length + 1)*20;
if ((parseInt(pos[1]) + parseInt(legendHeight)) > svgHeight) {
pos[1] = svgHeight - legendHeight -30;
}
this.legendSprite.attr('transform', 'translate('+ pos[0] +', '+ pos[1] +')');
this.legendSprite.select('rect').attr('width', maxWidth).attr('height', legendHeight);
},
hideLegend: function() {
this.legendSprite.style('display', 'none');
},
updateLegendContent: function(v) {
var me = this,
surface = this.surface,
legend = me.legend,// {displayLabels:[]}
displayLabels = legend.displayLabels || [],
displayLegendCls = this.displayLegendCls,
legendSprite = this.legendSprite,
legendRender = legend.rendeFn,
axis = this.axis,
v = function(){
return legendRender(v, axis.coordinateList[v.name].lines);
}(),
singalX = 25,
singalY = 20,
isArray = Ext.isArray,
x, len, item, ii, len1, preText, allTexts;
legend.displayLabels = displayLabels;
if (!legend.initialize) { //未初始化
for (i=0, len=displayLabels.length; i<len; i++) {
item = displayLabels[i];
legendSprite.append('svg:rect')
.attr('x', 7)
.attr('y', singalY - 10)
.attr('width', 10)
.attr('height', 10)
.attr('fill', '#ff8809')
legendSprite.append('svg:text')
.attr('x', singalX)
.attr('y', singalY)
.text('')
.attr('class', displayLegendCls);
singalY += 20;
}
legend.initialize = true;
}

allTexts = legendSprite.selectAll('text.' + displayLegendCls);
for (i=0, len=allTexts[0].length; i<len; i++) {
d3.select(allTexts[0][i]).text(replaceStrTemplate(displayLabels[i], v));
}
},
/**
* 绘制线
*/
drawLines: function() {
var me = this,
abs = Math.abs,
box = me.getBBox(),
storeDatas = this.store.data,
arrayDatas = [],
axis = this.axis,
coordinateList = axis.coordinateList,
series = this.series || {},
tipButtonSettings = this.tipButtonSettings,//tipButtonSettings.settings.colors = ['#E0EEE0', '#CD8162', '#FF0000'];
decideLineColor = function(d, i) {
var rgbs = coordinateList.rgbs, fn;
if (!rgbs ) {
rgbs = coordinateList.rgbs = {};
}
rgbs[i] = '#3D3D3D';
if ((fn = series.decideLineColor) && Ext.isFunction(fn)) {
rgbs[i] = fn(d, i, tipButtonSettings.settings.colors, tipButtonSettings.settings.expression);
}
return rgbs[i];
},
pi = Math.PI,
cos = Math.cos,
sin = Math.sin,
leftRightSpace = axis.leftRightSpace,
axisDatas = axis.data,
axisLableTopY = this.axisLableTopY,
vfield = axis.vField,
rfield = axis.rField,
lines,
diagonal = d3.svg.diagonal()
.source(function(d) {
var coordxy = coordinateList[d.get(vfield)];
return {x: coordxy.x, y: coordxy.y}
})
.target(function(d) {
var coordxy = coordinateList[d.get(rfield)];
return {x: coordxy.x, y: coordxy.y}
})
.projection(function(d, index, _arrayObj) {
var spoint = _arrayObj[0],
dpoint = _arrayObj[3],
cpoint, arrowX, arrowY, arrowL, isRight, randomNum;
randomNum = (Math.random()*10000)%(abs(leftRightSpace/5)) + 200;
if (spoint.x == dpoint.x) { //同一边
_arrayObj[1].x = (spoint.x == axis.rightx) ? spoint.x - 20 : spoint.x + 20;
if (spoint.y < dpoint.y ) {
_arrayObj[1].y = (spoint.y - axisLableTopY < 20) ? spoint.y : (spoint.y - 20);
} else {
_arrayObj[1].y = (spoint.y + 20);
}
_arrayObj[2].x = dpoint.x + (dpoint.x == axis.rightx ? (-1)*randomNum : randomNum);
_arrayObj[2].y = dpoint.y;
} else { //不同边
_arrayObj[1].x = spoint.x + (spoint.x == axis.rightx ? (-1)*randomNum : randomNum)
_arrayObj[1].y = spoint.y;
_arrayObj[2].x = dpoint.x + (dpoint.x == axis.rightx ? (-1)*randomNum : randomNum)
_arrayObj[2].y = dpoint.y;
}

//加入箭头
if (index == 3) {
arrowX = 8 * cos(pi/6);
arrowY = 8 * sin(pi/6);
isRight = (d.x == axis.rightx);
arrowX = (isRight ? d.x - arrowX : d.x + arrowX);
arrowL = 'L'+ arrowX +','+ (d.y-arrowY) +'M'+ d.x +','+ d.y +'L'+ arrowX +',' + (d.y+arrowY);
return [d.x, d.y + arrowL];
}
return [abs(d.x) + 0.5, abs(d.y) + 0.5];
});
//coordinateList[record.name] = {x: record.x, record.y, data: record.data};
this.surface.selectAll('path.' + me.lineItemCls).on('mouseover', null).on('mouseout', null).remove();
for (var i=0; i<storeDatas.length; i++) {
arrayDatas.push(storeDatas.get(i));
}
lines = this.surface.selectAll('path.' + me.lineItemCls)
.data(arrayDatas)
.enter()
.append('svg:path')
.attr('class', me.lineItemCls)
.attr('d', diagonal)
.attr('stroke', decideLineColor)
.attr('fill', 'none')
.attr('stroke-width', 1)
.style('cursor', function(d, i) {
//关联线与点的关系
coordinateList[d.get(vfield)].lines.push(i);
coordinateList[d.get(rfield)].lines.push(i);
coordinateList[i] = [d.get(vfield), d.get(rfield)];
return 'pointer';
})
.on('mouseover', function(d, i) {
d3.select(lines[0][i]).style('stroke', (series.mouseOverColor || 'rgb(109,152,36)'));
})
.on('mouseout', function(d, i) {
d3.select(lines[0][i]).style('stroke', coordinateList.rgbs[i]);
}).
on('click', function(d, i) {
me.showPortRelationSprite();
delayFn(me.onLinesClick, me, 200, [d, i]);
})
},
onLinesClick: function(d, i) {
this.fireEvent('onLinesClick', this, d, i);
},
/**
* 绘制点
*/
drawAxis: function() {
var me = this,
axis = this.axis,
alignCenter = axis.alignCenter || false,
leftSpace = axis.leftSpace,
coordinateList = axis.coordinateList,
leftRightSpace = axis.leftRightSpace,
axisLableSpace = axis.axisLableSpace,
leftSpritColor = axis.leftSpritColor,
rightSpritColor = axis.rightSpritColor,
series = this.series || {},
headerHeight = this.headerHeight,
axisLableTopY = this.axisLableTopY || headerHeight,
box = me.getBBox(),
axisDatas = axis.data, //axis.data = {leftTotal: 0, rightTotal: 0, total: 0, left: [], right: [], data: []};
xfn = function(d) {
return d.lx;
},
yfn = function(d) {
return d.ly;
},
lineItemCls = me.lineItemCls,
handerFocusLines = function(name, isFocus, otherHidden) {
var lines = axis.coordinateList[name].lines,
allLines = d3.selectAll('path.' + lineItemCls)[0],
i, len;
for (i=0, len=allLines.length; i<len; i++) {
d3.select(allLines[i]).style('display', (otherHidden ? 'none' : ''));
}
for (i=0, len=lines.length; i<len; i++) {
d3.select(allLines[lines[i]]).style('stroke', (isFocus ? (series.mouseOverColor || 'rgb(109,152,36)') : coordinateList.rgbs[lines[i]])).style('display', '');
}
},
textWidth, i, len, record, svgPathD, svgGs, datas, coordinateList;

this.surface.selectAll('path.' + this.surfaceAxisLeftCls).remove();
this.surface.selectAll('path.' + this.surfaceAxisRightCls).remove();
this.surface.selectAll('g.' + this.surfaceAxisLeftLabelCls).remove();
this.surface.selectAll('g.' + this.surfaceAxisRightLabelCls).remove();
if (alignCenter) {
leftSpace = ((box.width - leftRightSpace) / 2);
}
//绘制title
if (!axis.leftTitleSprit) {
axis.leftTitleSprit = this.surface.append('svg:g');
axis.leftTitleSprit.append('svg:rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', 150)
.attr('height', 20)
.attr('rx', 0)
.attr('ry', 0)
.attr('fill', 'none')
.attr('stroke-width', 1)
.attr('stroke', '#f0a50a')
axis.leftTitleSprit.append('svg:circle')
.attr('cx', 15)
.attr('cy', 10)
.attr('r', 4)
.attr('fill', '#f0a50a')
axis.leftTitleSprit.append('svg:text')
.text(axis.leftTitle)
.style('font-size', '11px')
.attr('x', 25)
.attr('y', 15)
.attr('fill', '#f0a50a');
}
if (!axis.rightTitleSprit) {
axis.rightTitleSprit = this.surface.append('svg:g');
axis.rightTitleSprit.append('svg:rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', 150)
.attr('height', 20)
.attr('rx', 0)
.attr('ry', 0)
.attr('fill', 'none')
.attr('stroke-width', 1)
.attr('stroke', '#c20024')
axis.rightTitleSprit.append('svg:circle')
.attr('cx', 15)
.attr('cy', 10)
.attr('r', 4)
.attr('fill', '#c20024')
axis.rightTitleSprit.append('svg:text')
.text(axis.rightTitle)
.style('font-size', '11px')
.attr('x', 25)
.attr('y', 15)
.attr('fill', '#c20024');
}

//计算axis title的宽度
axis.leftx = (leftSpace + 10);
axis.rightx = (leftSpace + leftRightSpace);
this.leftSpace = leftSpace;
axis.leftRightSpace = leftRightSpace;

textWidth = axis.leftTitleSprit.select('text')[0][0].offsetWidth + 40;
axis.leftTitleSprit.select('rect').attr('width', textWidth);
axis.leftTitleSprit.attr('transform', 'translate('+ (axis.leftx - textWidth) +', 40)');

textWidth = axis.rightTitleSprit.select('text')[0][0].offsetWidth + 40;
axis.rightTitleSprit.select('rect').attr('width', textWidth);
axis.rightTitleSprit.attr('transform', 'translate('+ (axis.rightx) +', 40)');
coordinateList = axis.coordinateList = {};
//draw left
svgPathD = 'M' + axis.leftx + ','+ axisLableTopY +'L';
for (i=0, len=axisDatas.left.length; i<len; i++) {
record = axisDatas.left[i];
record.x = axis.leftx;
record.y = axisLableTopY + (i*axisLableSpace);
record.lx = (10 + (calculateAxisLabelFn(record.name)))*(-1);
record.ly = record.y;
coordinateList[record.name] = {x: record.x, y: record.y, data: record.data, lines: []};
svgPathD = svgPathD + (record.x + 0.5) + ',' + (record.y+ 0.5) +'M'+ (record.x+ 0.5) +','+ (record.y+ 0.5) + 'L'+ (record.x-10 + 0.5) +',' + (record.y+ 0.5) + 'M' + (record.x+ 0.5) + ',' + (record.y+ 0.5);
if (i < (len-1)) {
svgPathD = svgPathD + 'L';
}
}

this.surface.append('svg:path').attr('d', svgPathD).attr('fill', 'none').attr('stroke', leftSpritColor).attr('stroke-width',1).attr('class', this.surfaceAxisLeftCls);
svgPathD = 'M' + axis.rightx + ','+ axisLableTopY +'L';
for (i=0, len=axisDatas.right.length; i<len; i++) {
record = axisDatas.right[i];
record.x = axis.rightx;
record.y = axisLableTopY + (i*axisLableSpace);
record.lx = 10;
record.ly = record.y;
coordinateList[record.name] = {x: record.x, y: record.y, data: record.data, lines: []};
svgPathD = svgPathD + (record.x + 0.5) + ',' + (record.y+ 0.5) +'M'+ (record.x+ 0.5) +','+ (record.y+ 0.5) + 'L'+ (record.x+10 + 0.5) +',' + (record.y+ 0.5) + 'M' + (record.x+ 0.5) + ',' + (record.y+ 0.5);
if (i < (len-1)) {
svgPathD = svgPathD + 'L';
}
}
this.surface.append('svg:path').attr('d', svgPathD).attr('fill', 'none').attr('stroke', rightSpritColor).attr('stroke-width', 1).attr('class', this.surfaceAxisRightCls);
//draw axis label
i=0;
var cls, fill;
while((i++) < 2) {
cls = (i == 1 ? this.surfaceAxisLeftLabelCls : this.surfaceAxisRightLabelCls);
fill = (i == 1 ? leftSpritColor : rightSpritColor);
svgGs = this.surface.selectAll('g.' + cls).data((i == 1 ? axisDatas.left : axisDatas.right)).enter()
.append('svt:g')
.attr('class', cls)
.attr('transform', function(d) {
if (i == 1) {
return 'translate('+ (d.x) +','+ (d.ly-2) +')';
}
return 'translate('+ (d.x) +','+ (d.ly-2) +')';

});
svgGs.append('svg:circle')
.attr('cx', (i == 1 ? -6 : 6))
.attr('cy', 2)
.attr('r', 3)
.attr('fill', '#000')
.style('display', 'none')
.attr('stroke', fill);
svgGs.append('svg:text')
.attr('x', xfn)
.attr('y', 5)
.text(function(d){ return d.name })
.attr('fill', fill)
.on('mouseover', function(d, i) {
var surface = me.surface,
gs, dobj;
gs = surface.selectAll('g.' + (d.position == 1 ? me.surfaceAxisLeftLabelCls : me.surfaceAxisRightLabelCls));
dobj = d3.select(gs[0][i]);
dobj.select('circle').style('display', '');
dobj.select('text').style('fill', 'green');
if (currentSelectScale != d && currentSelectScale != null) {
handerFocusLines(currentSelectScale.name, false, false);
}
handerFocusLines(d.name, true, false);

me.updateLegendContent(d);
me.showLegend(getXYFromTranslate(dobj.attr('transform')));
me.hidePortRelationSprite();
})
.on('mouseout', function(d, i) {
var surface = me.surface,
gs, dobj;
gs = surface.selectAll('g.' + (d.position == 1 ? me.surfaceAxisLeftLabelCls : me.surfaceAxisRightLabelCls));
dobj = d3.select(gs[0][i]);
//dobj.select('circle').style('fill', (d.position == 1 ? '#f0a50a' : '#c20024'));
dobj.select('circle').style('display', 'none');
dobj.select('text').style('fill', (d.position == 1 ? leftSpritColor : rightSpritColor));
if (currentSelectScale != d) {
handerFocusLines(d.name, false, false);
currentSelectScale = null;
}
me.hideLegend();
me.hidePortRelationSprite();
})
.on('click', function(d, i) {
me.hidePortRelationSprite();
currentSelectScale = d;
handerFocusLines(d.name, false, true);
})
.style('cursor', 'pointer')
}
},
//--------------------------------tipButtons-------------------------------------
showTipButton: function() {
var me = this,
tipButtonCfg = this.tipButtonCfg || {},
box = this.getBBox(),
tipButtonSprite = this.tipButtonSprite,
y, tipButtonSpriteDom;
tipButtonCfg.collapsedBtnCls = tipButtonCfg.collapsedBtnCls || 'tip-cfg-collapsed-btn-cls';
tipButtonCfg.width = tipButtonCfg.width || 250;
tipButtonCfg.height = tipButtonCfg.height || 210;
this.tipButtonCfg = tipButtonCfg;
if (this.drawStatus == 1) {
tipButtonSpriteDom = this.tipButtonSprite[0][0];
//解决覆盖问题
this.tipButtonSprite.remove();
this.surface[0][0].appendChild(tipButtonSpriteDom);
}
if (!tipButtonSprite) {
tipButtonSprite = this.tipButtonSprite = this.surface.append('svg:g')
.on('mouseover', function() {
//me.showTipButton();
})
.on('mouseout', function() {
//me.hideTipButton();
});
tipButtonSprite.settings = this.tipButtonSettings.settings;
tipButtonSprite.textBtn = tipButtonSprite.append('svg:circle')
.attr('class', tipButtonCfg.collapsedBtnCls)
.attr('cx', 25)
.attr('r', 25)
.attr('r', 25)
.style('stroke-width', 1)
.style('stroke', '#FFFF00')
.attr('fill', '#FFFF00')
.style('opacity', 1)
.attr('cy', (tipButtonCfg.height/2-25))

//tipButtonSprite.textBtn = tipButtonSprite.append('svg:rect')
tipButtonSprite.append('svg:rect')
.attr('x', 50)
.attr('width', tipButtonCfg.width)
.attr('height', tipButtonCfg.height)
.attr('fill', '#000')
.style('stroke-width', 1)
.style('stroke', 'none')
.style('opacity', 0.5)
.style('stroke-opacity', 0)

tipButtonSprite.d3SettingSprite = tipButtonSprite.selectAll('g.btn-settings').data(tipButtonSprite.settings.data).enter()
.append('svg:g')
.attr('transform', function(d, i) {
return 'translate(60, '+ (20 + i*80) +')';
})
.attr('class', function(d, i) {return 'btn-settings btn-settings-'+i});

tipButtonSprite.d3SettingSprite.append('svg:rect')
.attr('x', 5)
.attr('width', 20)
.attr('height', 20)
.attr('fill', function(d, i) {return tipButtonSprite.settings.colors[i]})
.attr('y', 2)
tipButtonSprite.d3SettingSprite.append('svg:text')
.attr('class', 'real-value-cls')
.text('')
.attr('x', 15)
.style('fill', '#FFF')
.attr('y', 40)
tipButtonSprite.d3SettingSprite.append('svg:text')
.attr('class', 'per-value-cls')
.text('')
.attr('x', 15)
.style('fill', '#FFF')
.attr('y', 70)
//btn----add
tipButtonSprite.d3SettingSprite.append('svg:rect')
.attr('class', 'add-btn-cls')
.attr('x', 130)
.attr('y', 30)
.attr('rx', 2)
.attr('rx', 2)
.attr('width', 16)
.attr('height', 16)
.style('fill', '#FFF')
.style('cursor', 'pointer')
.style('display', function(d, i) {
return i == 2 ? 'none' : '';
})
.on('mouseover', function(d, i) {
tipButtonSprite.select('g.btn-settings-' + i).select('rect.add-btn-cls').style('fill', '#FF82AB')
})
.on('mouseout', function(d, i) {
tipButtonSprite.select('g.btn-settings-' + i).select('rect.add-btn-cls').style('fill', '#FFF')
})
.on('click', function(d, i) {
var expression = tipButtonSprite.settings.expression,
v = expression[i].lessthan[0];
v += 1000000;
expression[i].lessthan[0] = v;
me.updateTipButtonContext();
})

tipButtonSprite.d3SettingSprite.append('svg:path')
.attr('d', 'M130,38L146,38M138,31L138,44')
.style('stroke', '#000')
.style('stroke-width', 1.5)
.style('cursor', 'pointer')

//btn----sub
tipButtonSprite.d3SettingSprite.append('svg:rect')
.attr('class', 'sub-btn-cls')
.attr('x', 160)
.attr('y', 30)
.attr('rx', 2)
.attr('rx', 2)
.attr('width', 16)
.attr('height', 16)
.style('fill', '#FFF')
.style('cursor', 'pointer')
.style('display', function(d, i) {
return i == 2 ? 'none' : '';
})
.on('mouseover', function(d, i) {
tipButtonSprite.select('g.btn-settings-' + i).select('rect.sub-btn-cls').style('fill', '#FF82AB')
})
.on('mouseout', function(d, i) {
tipButtonSprite.select('g.btn-settings-' + i).select('rect.sub-btn-cls').style('fill', '#FFF')
})
.on('click', function(d, i) {
var expression = tipButtonSprite.settings.expression,
v = expression[i].lessthan[0];
v -= 1000000;
expression[i].lessthan[0] = v > 0 ? v : 0;
me.updateTipButtonContext();
})
tipButtonSprite.d3SettingSprite.append('svg:path')
.attr('d', 'M160,38L176,38')
.style('stroke', '#000')
.style('stroke-width', 1.5)
.style('cursor', 'pointer')

//btn----add-- per
tipButtonSprite.d3SettingSprite.append('svg:rect')
.attr('class', 'per-add-btn-cls')
.attr('x', 130)
.attr('y', 60)
.attr('rx', 2)
.attr('rx', 2)
.attr('width', 16)
.attr('height', 16)
.style('fill', '#FFF')
.style('cursor', 'pointer')
.style('display', function(d, i) {
return i == 2 ? 'none' : '';
})
.on('mouseover', function(d, i) {
tipButtonSprite.select('g.btn-settings-' + i).select('rect.per-add-btn-cls').style('fill', '#FF82AB')
})
.on('mouseout', function(d, i) {
tipButtonSprite.select('g.btn-settings-' + i).select('rect.per-add-btn-cls').style('fill', '#FFF')
})
.on('click', function(d, i) {
var expression = tipButtonSprite.settings.expression,
v = expression[i].lessthan[1];
v = toFix(v + 0.0002, 4) ;
expression[i].lessthan[1] = v;
me.updateTipButtonContext();
})
tipButtonSprite.d3SettingSprite.append('svg:path')
.attr('d', 'M130,68L146,68M138,61L138,75')
.style('stroke', '#000')
.style('stroke-width', 1.5)
.style('cursor', 'pointer')

//btn----sub-- per
tipButtonSprite.d3SettingSprite.append('svg:rect')
.attr('class', 'per-sub-btn-cls')
.attr('x', 160)
.attr('y', 60)
.attr('rx', 2)
.attr('rx', 2)
.attr('width', 16)
.attr('height', 16)
.style('fill', '#FFF')
.style('cursor', 'pointer')
.style('display', function(d, i) {
return i == 2 ? 'none' : '';
})
.on('mouseover', function(d, i) {
tipButtonSprite.select('g.btn-settings-' + i).select('rect.per-sub-btn-cls').style('fill', '#FF82AB')
})
.on('mouseout', function(d, i) {
tipButtonSprite.select('g.btn-settings-' + i).select('rect.per-sub-btn-cls').style('fill', '#FFF')
})
.on('click', function(d, i) {
var expression = tipButtonSprite.settings.expression,
v = expression[i].lessthan[1];
v = toFix(v - 0.0002, 4) ;
expression[i].lessthan[1] = v > 0 ? v : 0;
me.updateTipButtonContext();
});
tipButtonSprite.d3SettingSprite.append('svg:path')
.attr('d', 'M160,68L176,68')
.style('stroke', '#000')
.style('stroke-width', 1.5)
.style('cursor', 'pointer');

this.updateTipButtonContext();
}
tipButtonSprite.collapsed = false;
this.updateTipButtonPosition();
tipButtonSprite.textBtn.style('display', 'none');
},
updateTipButtonContext: function() {
var me=this,
tipButtonSprite = this.tipButtonSprite,
j=0;
while(j++ < 2) {
tipButtonSprite.select('g.btn-settings-' + (j-1)).select('text.real-value-cls').text('实际流量小于:' + (tipButtonSprite.settings.expression[j-1].lessthan[0]/1000000) + 'm');
tipButtonSprite.select('g.btn-settings-' + (j-1)).select('text.per-value-cls').text('百分比小于:' + (tipButtonSprite.settings.expression[j-1].lessthan[1]*100) + '%');
}
if (this.drawStatus == 1) {
if (me.tipButtonTaskId) { //清楚旧的
clearTimeout(me.tipButtonTaskId);
me.tipButtonTaskId = null;
}
me.tipButtonTaskId = setTimeout(function() {
me.drawLines();
}, 1000)
}
},

hideTipButton: function() {
this.tipButtonSprite.collapsed = true;
this.updateTipButtonPosition();
this.tipButtonSprite.textBtn.style('display', '');
},
updateTipButtonPosition: function(pos) {
var box = this.getBBox(),
tipButtonCfg = this.tipButtonCfg,
tipButtonSprite = this.tipButtonSprite,
pos = pos || [(tipButtonSprite.collapsed ? (box.width -50) : box.width -tipButtonCfg.width-50), (box.scrollTop - 100 + (box.height/2))];
tipButtonSprite.attr('transform', 'translate('+ pos[0] +', '+ pos[1] +')');
},
//---------------------------port 关联关系图----------------------------------------
drawPortRelationSprite: function() {
var me=this,
portRelationSprite = this.portRelationSprite,
height = 210,
topTitleY = 20,
bottomTitleY = 200,
drawCmpHeight = 100,
space = 40,
axisCount = 1000,
pathTopD = 'M0,30.5L' + space + ',30.5', pathBottomD = 'M0,'+(30.5 + drawCmpHeight)+'L'+ space +',' + (30.5 + drawCmpHeight),
drawMorror = {},
startDragFn = function() {
drawMorror.dragstatus = true;
drawMorror.currentX = event.offsetX;
},
endDragFn = function() {
drawMorror.dragstatus = false;
},
onMoveFn = function() {
if (drawMorror.dragstatus !== true) {return;}
var dddd = portRelationSprite.attr('transform');
var xy = getXYFromTranslate(portRelationSprite.attr('transform')) || [0, 0],
box = me.getBBox(), shouldX;
shouldX = (xy[0] + (event.offsetX-drawMorror.currentX));
if (Math.abs(shouldX - box.width)> portRelationSprite.realWidth) { //结束
shouldX = (box.width - portRelationSprite.realWidth);
}
if (shouldX > 0) {shouldX = 0}
portRelationSprite.attr('transform', 'translate(' + shouldX + ', ' + xy[1] + ')');
drawMorror.currentX = event.offsetX;
},
i, sprtes;
if (!portRelationSprite) {
portRelationSprite = this.portRelationSprite = this.surface.append('svg:g').attr('fill', '#FFF');
portRelationSprite.realWidth = (axisCount + 3) * space;
portRelationSprite.bodyCmp = portRelationSprite.append('svg:rect')
.attr('x',0)
.attr('y', 0)
.attr('width', portRelationSprite.realWidth)
.attr('height', height)
.attr('fill', '#000')
.style('opacity', 0.7);
portRelationSprite.topTitleSprite = portRelationSprite.append('svg:text').text('Top Title').attr('y', topTitleY).attr('x', 20);
portRelationSprite.bottomTitleSprite = portRelationSprite.append('svg:text').text('Bottom Title').attr('y', bottomTitleY).attr('x', 20);
portRelationSprite.drawCmpSprite = portRelationSprite.append('svg:g').attr('transform', 'translate(0, '+(topTitleY+10)+')');
//初始化axis
for (i=0; i<axisCount; i++) {
pathTopD = pathTopD + 'M'+(space*(i+1))+',30.5L'+ (space*(i+1)) +',20.5M'+ (space*(i+1)) +',30.5L'+ (space*(i+2)) +',30.5';
pathBottomD = pathBottomD + 'M'+(space*(i+1))+','+(30.5 + drawCmpHeight)+'L'+ (space*(i+1)) +','+(40.5 + drawCmpHeight)+'M'+ (space*(i+1)) +','+(30.5 + drawCmpHeight)+'L'+ (space*(i+2)) +',' + (30.5 + drawCmpHeight);
}
portRelationSprite.drawCmpSprite.append('svg:path').attr('d', pathTopD).attr('stroke', '#f0a50a');
portRelationSprite.drawCmpSprite.append('svg:path').attr('d', pathBottomD).attr('stroke', '#c20024');

portRelationSprite.height = height;
portRelationSprite.topTitleY = topTitleY;
portRelationSprite.bottomTitleY = bottomTitleY;
portRelationSprite.drawCmpHeight = drawCmpHeight;
portRelationSprite.space = space;
portRelationSprite.axisCount = axisCount;
}
sprtes = [portRelationSprite, portRelationSprite.bodyCmp];
Ext.each(sprtes, function(v) {
v.on('mousedown', startDragFn)
v.on('mouseup', endDragFn)
v.on('mousemove', onMoveFn);
v.style('cursor', 'move')
});
},
showPortRelationSprite: function(x) {
var portRelationSpriteDom,
box = this.getBBox(),
portRelationSprite = this.portRelationSprite,
realPos;
if (!portRelationSprite) {
this.drawPortRelationSprite();
portRelationSprite = this.portRelationSprite;
}
if (this.drawStatus == 1) {
portRelationSpriteDom = portRelationSprite[0][0];
portRelationSprite.remove();
this.surface[0][0].appendChild(portRelationSpriteDom);
}
realPos = getXYFromTranslate(portRelationSprite.attr('transform'));
x = x || (!!realPos ? realPos[0] : 0);
portRelationSprite.attr('transform', 'translate('+ x +', ' + box.scrollTop + ')');
portRelationSprite.style('display', '');

},
hidePortRelationSprite: function() {
if (this.portRelationSprite) {
this.portRelationSprite.style('display', 'none');
}
},
//绘制端口曲线
/**
* options = {vField, vLabelField, rField, rLableField, topLabelTile, bottomLableTitle, sizeField}
*/
loadPortDatas: function(storeDatas, options) {
var series = this.series,
portRelationSprite = this.portRelationSprite,
datas = portRelationSprite.data = {nodes: [], topDatas: [], bottomDatas: [], topCount: 0, bottomCount: 0, topLabelTile: 'T', bottomLableTitle: 'B', allCoordinate: {}},
i, len = storeDatas.length, record, port, ip, obj, has = {}, pro, axisEs = [],
pi = Math.PI,
cos = Math.cos,
abs = Math.abs,
sin = Math.sin,
floor = Math.floor,
decideLineColor = function(d, i) {
var rgbs = '#1C86EE';
if ((fn = series.decidePortLineColor) && Ext.isFunction(fn)) {
rgbs = fn(d, i);
}
return rgbs;
},
diagonal = d3.svg.diagonal()
.source(function(d) {
var cxy = datas.allCoordinate[d.get(options.vLabelField) + ':' + d.get(options.vField)];
return {x:cxy[0], y: cxy[1]};})
.target(function(d) {var cxy = datas.allCoordinate[d.get(options.rLableField) + ':' + d.get(options.rField)];return {x:cxy[0], y: cxy[1]};})
.projection(function(d, index, _arrayObj) {
var spoint = _arrayObj[0],
dpoint = _arrayObj[3],
sx = spoint.x,
dx = dpoint.x,
spos = spoint.y < dpoint.y;
if (index == 1) {//firt C
d.x = (sx < dx ? (sx - 30) : (sx + 30));
d.y = spos ? (d.y + 20) : (d.y - 20);
}
if (index == 2) {//TWO C
d.x = dpoint.x;
d.y = spos ? (d.y + 10) : (d.y - 10);
}
//加入箭头
if (index == 3) {
arrowX = 8 * sin(pi/6);
arrowY = 8 * cos(pi/6);
arrowL = 'M' + d.x + ',' + d.y + 'L' + (d.x-arrowX) + ',' + (spos ? (d.y-arrowY) : (d.y + arrowY)) + 'M' + d.x + ',' + d.y + 'L' + (d.x+arrowX) + ',' + (spos ? (d.y-arrowY) : (d.y + arrowY));
return [d.x, d.y + arrowL];
}
return [abs(d.x) + 0.5, abs(d.y) + 0.5];
});

portRelationSprite.drawCmpSprite.selectAll('text.port-axis-top-item-cls').remove();
portRelationSprite.drawCmpSprite.selectAll('text.port-axis-bottom-item-cls').remove();
portRelationSprite.drawCmpSprite.selectAll('path.port-lines-item-cls').remove();
portRelationSprite.drawCmpSprite.selectAll('text.port-lines-item-text-cls').remove();

for (i=0; i<len; i++) {
record = storeDatas.get(i);
port = record.get(options.vField);
ip = record.get(options.vLabelField);
pro = ip + ':' + port;
if (!has[pro]) {
obj = {data: record, port: port, ip: ip, field: options.vField};
datas.nodes.push(obj);
if (ip == options.topLabelTile) {
datas.topDatas.push(obj);
datas.topCount = datas.topCount + 1;
obj.pos = 1;
} else {
datas.bottomDatas.push(obj);
datas.bottomCount = datas.bottomCount + 1;
obj.pos = 2;
}
has[pro] = true;
}
port = record.get(options.rField);
ip = record.get(options.rLableField);
pro = ip + ':' + port;
if (!has[pro]) {
obj = {data: record, port: port, ip: ip, field: options.vField};
datas.nodes.push(obj);
if (ip == options.topLabelTile) {
datas.topDatas.push(obj);
datas.topCount = datas.topCount + 1;
obj.pos = 1;
} else {
datas.bottomDatas.push(obj);
datas.bottomCount = datas.bottomCount + 1;
obj.pos = 2;
}
has[pro] = true;
}
}
datas.topLabelTile = options.topLabelTile
datas.bottomLableTitle = options.bottomLableTitle

//绘制PORT节点
axisEs.push(portRelationSprite.drawCmpSprite.selectAll('text.port-axis-top-item-cls').data(datas.topDatas).enter())
axisEs.push(portRelationSprite.drawCmpSprite.selectAll('text.port-axis-bottom-item-cls').data(datas.bottomDatas).enter())
Ext.each(axisEs, function(v, index) {
var y = (index == 0 ? (17) : (portRelationSprite.drawCmpHeight+50));
v.append('svg:text')
.text(function(d, i) {return d.port})
.style('fill', (index == 0 ? '#f0a50a' : '#c20024'))
.attr('class', (index == 0 ? 'port-axis-top-item-cls' : 'port-axis-bottom-item-cls'))
.attr('x', function(d, i) {
var x = (i+1)*portRelationSprite.space - (calculateAxisLabelFn(d.port) /2);
if (d.pos == 1) { //起始点
datas.allCoordinate[d.ip + ':' + d.port] = [x + (calculateAxisLabelFn(d.port) /2), y + 10];
} else {
datas.allCoordinate[d.ip + ':' + d.port] = [x + (calculateAxisLabelFn(d.port) /2), y-20];
}
return x
})
.attr('y', y);
})
portRelationSprite.topTitleSprite.text(datas.topLabelTile)
portRelationSprite.bottomTitleSprite.text(datas.bottomLableTitle);

//绘制线
var lineDatas = [];
for (i=0, len = storeDatas.length; i<len; i++) {
lineDatas.push(storeDatas.get(i));
}
portRelationSprite.drawCmpSprite.selectAll('path.port-lines-item-cls').data(lineDatas).enter()
.append('svg:path')
.attr('class', 'port-lines-item-cls')
.attr('d', diagonal)
.style('stroke', decideLineColor)
.style('stroke-width', 1)
.style('fill', 'none')

portRelationSprite.drawCmpSprite.selectAll('text.port-lines-item-text-cls').data(lineDatas).enter()
.append('svg:text')
.attr('class', 'port-lines-item-text-cls')
.text(function(d, i) {
return toFix((d.get(options.sizeField) / 1000000), 2) + 'M';
})
.style('fill', '#00CD00')
.style('stroke-rotate', 30)
.attr('transform', function(d, i) {
var sxy = datas.allCoordinate[d.get(options.vLabelField) + ':' + d.get(options.vField)],
dxy = datas.allCoordinate[d.get(options.rLableField) + ':' + d.get(options.rField)],
d = {y: sxy[1]},
sx = sxy[0],
dx = dxy[0];
stop = sxy[1] < dxy[1];
d.x = (sx < dx ? (sx - 30) : (sx + 30));
d.y = stop ? (d.y + 25) : (d.y - 25);
return 'translate('+ (d.x - 40) +', '+ d.y +') rotate(45)';
})
this.showPortRelationSprite(1);
},

onDestroy: function() {
this.getEl().removeListener('scroll', this.onScrollEvent, this);
this.callParent(arguments);
}
}
});


[/align]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值