PS: 这篇文章适合有一定D3基础的童鞋,因为没有多余的注释,只是笔者比较业余的代码分享,勿喷
目录
- HTML
- JS
- CSS
- 效果图
^ HTML
<div class="content-panel">
<div id="object"></div>
<div id="object-arrow"></div>
</div>
^ JS
(angularjs Controller的代码)
var url = $location.path();
$scope.$storage = $localStorage;
var defaultLocation;
if (_.has($localStorage, 'multiObject')) {
defaultLocation = $localStorage.multiObject;
}
var nodeNameMap = {};
var newNodes = _.map(abjectData.nodes, function(node) {
nodeNameMap[node.objectId] = node.name;
var rNode = {};
if (_.has(defaultLocation, node.objectId)) {
rNode.name = node.name;
rNode.objectId = node.objectId;
rNode.instanceCount = + node.instanceCount;
rNode.attrs = node.attrs;
rNode.fixed = true;
rNode.x = defaultLocation[node.objectId].x;
rNode.y = defaultLocation[node.objectId].y;
} else {
rNode = node;
}
return rNode;
});
var width = window.innerWidth - 250,
height = window.innerHeight - 200,
boxWidth = 160,
countR = 15;
var objectContainer = d3.select('#object').append('div')
.style('position', 'absolute')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force();
force
.nodes(newNodes)
.links(abjectData.links)
.size([width, height])
.linkDistance(200)
.charge(-2000)
.chargeDistance(250)
.start();
var svg = d3.select('#object-arrow').append('svg')
.attr('width', width)
.attr('height', height);
svg.append('defs').append('marker')
.attr('id', 'end-arrow')
.attr('refX', 4)
.attr('refY', 4)
.attr('viewBox', '0 0 8 8')
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,0 8 4 0 8 4 4z')
.attr('fill', '#7FABD2')
.attr('stroke', 'none');
var path = svg.append('g').selectAll('path')
.data(force.links())
.enter().append('path')
.attr('class', 'link')
.style('marker-end', 'url(' + url + '#end-arrow)');
var objectEnter = objectContainer.selectAll('div')
.data(newNodes)
.enter();
var object = objectEnter.append('div')
.attr('class', 'object')
.attr('id', function (d) {
return d.objectId;
});
var objectHeader = object.append('div')
.attr('class', 'object-header')
.append('i')
.attr('class', function(d) {
return objectIconMap(d.objectId);
})
.text(function (d) {
return ' ' + d.name;
});
object.append('div')
.attr('class', 'object-attrs')
.selectAll('p')
.data(function(d) {
return _.uniq(d.attrs);
})
.enter()
.append('p')
.text(function(d) {
return nodeNameMap[d];
});
var objectToolBar = object.append('div')
.attr('class', 'object-settings');
objectToolBar.append('a')
.attr('class', 'btn btn-primary btn-link btn-xs')
.attr('href', function (d) {
return url + 'attr/' + d.objectId;
})
.append('i')
.attr('class', 'glyphicon glyphicon-cog');
object.on('dblclick', function(d) {
d3.select(this).classed('fixed', d.fixed = false);
}).call(force.drag().on('dragstart', function(d) {
d3.select(this).classed('fixed', d.fixed = true);
})
);
objectHeader.on('click', function (d) {
if (d3.event.defaultPrevented) { return; }
//然后根据 d 的数据,做一些个性化的跳转判断, Dialog或$state.go('xx')等
});
force.on('tick', function() {
path.attr('d', drawPath);
object.style('left', function (d) {
return Math.max(Math.min(d.x, width), 0) + 'px';
}).style('top', function (d) {
return Math.max(Math.min(d.y, height), countR) + 'px';
});
});
function objectIconMap(objectId) {
return OBJECTICONMAP[objectId] || 'ti-layout-media-overlay-alt';
}
//箭头定位算法, 明显比较挫,看效果就知道了喽
function drawPath(d) {
var dx = Math.abs(d.target.x - d.source.x),
dy = Math.abs(d.target.y - d.source.y),
headerHeight = 40,
itemHeight = 25,
arrowWidth = 5,
itemStart = (_.indexOf(d.source.attrs, d.target.objectId) + 1) * itemHeight - itemHeight / 2,
x1 = d.source.x + boxWidth,
y1 = d.source.y + headerHeight + itemStart,
x2 = x1 + dx / 4,
y2 = y1,
x4 = d.target.x - arrowWidth,
y4 = d.target.y + headerHeight / 2,
x3 = x4 - dx / 4,
y3 = y4;
if ((x1 - boxWidth) <= x4 && d.source.y >= d.target.y) {
if (x1 > x4) {
x1 -= boxWidth;
x2 = Math.max(x1 - dx, 0);
y2 -= dy / 4;
x3 = Math.max(x4 - dx, 0);
y3 = y4 + dy / 4;
}
} else if ((x1 - boxWidth) <= x4 && d.source.y < d.target.y) {
if (x1 > x4) {
x1 -= boxWidth;
x2 = Math.max(x1 - dx, 0);
y2 += dy / 2;
x3 = Math.max(x4 - dx, 0);
}
} else {
if ((x1 - boxWidth) < (x4 + boxWidth)) {
x2 = Math.max(x1 + dx, 0);
x4 += boxWidth + 2 * arrowWidth;
x3 = Math.max(x4 + dx, 0);
} else {
x1 -= boxWidth;
x2 = Math.max(x1 - dx / 4, 0);
x4 += boxWidth + 2 * arrowWidth;
x3 = Math.max(x4 + dx / 4, 0);
}
}
if (x1 + arrowWidth === x4) {
x2 = x1 + itemHeight + arrowWidth;
x3 = x2;
}
return 'M' + x1 + ',' + y1 + ' C' + x2 + ',' + y2 + ' ' + x3 + ',' + y3 + ' ' + x4 + ',' + y4;
}
var save = function() {
var defaultLocation = {};
angular.forEach(object[0], function(node) {
defaultLocation[node.id] = {
x: node.offsetLeft,
y: node.offsetTop
};
});
$localStorage.multiObject = defaultLocation;
toastr.success('保存成功');
};
$scope.save = save;
}
^ CSS
scss->css
path.link {
fill: none;
stroke: $brand-info;
stroke-width: 1.5px;
}
.object {
width: 160px;
position: absolute; /* 注意 */
// .object-settings {
// visibility: hidden;
// visibility: visible;
// }
// &:hover {
// .object-settings {
// padding: 5px;
// background-color: $gray-lighter;
// border: 1.5px solid $btn-primary-img-border;
// border-top-width: 0px;
// border-bottom-right-radius:0.3em;
// border-bottom-left-radius:0.3em;
// text-align: center;
// visibility: visible;
// }
// }
}
.object-settings {
padding: 5px;
background-color: #FFFFFF;
border: 1.5px solid $btn-primary-img-border;
border-top-width: 0px;
border-bottom-right-radius:0.3em;
border-bottom-left-radius:0.3em;
text-align: center;
}
.object-attrs {
margin-top: 40px;
width: 160px;
background-color: #FFFFFF;
}
.object-attrs > p {
text-align: center;
margin: 0px;
font-size: 15px;
font-weight: normal;
height: 25px;
overflow: hidden;
text-overflow: ellipsis;
color: $brand-primary-dark;
border: 1.5px solid $btn-primary-img-border;
border-top-width: 0px;
}
.object-header {
position: absolute;
cursor: pointer;
border-top-left-radius:0.2em;
border-top-right-radius:0.2em;
border-bottom-right-radius:0em;
border-bottom-left-radius:0em;
background-color: $brand-primary-dark;
color: #FFF;
width: 160px;
height: 40px;
font-size: 26px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
}
^ 效果图
参考
时间: 2024-09-29 12:32:11