I want to visualize data as a tree, but also I want to customize default link appearance. There shown default appearance, but I want to create links which looks like Rational Software Architect links. Is it possible?
The links are SVG path elements. You can style them using CSS to change the color, width etc. For the arrow heads, you can use SVG Markers. To add labels, you would need to add additional SVG text elements. You could for example add a new select with the tree links as data that create the SVG text elements for the UML cardinality.
I've done it with writing my own path handler. Here is the sample code:
function elbow(d) {
var radius = 10;
var xOffsetSign = Math.sign(d.source.x - d.target.x);
var yOffsetSign = Math.sign(d.source.y - d.target.y);
if (xOffsetSign != 0) {
var ellipseXDirection = (xOffsetSign * yOffsetSign) > 0 ? 1 : 0;
return "M" + d.source.x + "," + d.source.y
+ " H" + (d.target.x + xOffsetSign * radius)
+ " A" + radius + "," + radius + " 0 0," + ellipseXDirection + " " + d.target.x + "," + (d.source.y - yOffsetSign * radius)
+ " V" + d.target.y
+ (d.target.children ? "" : "h" + margin.right);
} else {
return "M" + d.source.x + "," + d.source.y
+ " H" + d.target.x + " V" + d.target.y
+ (d.target.children ? "" : "h" + margin.right);
}
}
Function Math.sign is my own implementation
Related
I have some colored text to indicate quality (red = bad, green = good).
When the quality is at 50%, the text is yellow and barely legible. Is there any way to have a gradient go from red to green without yellow in the middle?
(The quality could be any value, so setting the colors manually won't work.)
for(i=0;i<=100;i+=10) {
$('body').append('<div style="color:' + color(i/100) + '">Quality ' + i + '</div>');
}
function color(quality) {
var h = 355 + 125 * quality;
var s = 130 - 60 * quality;
var l = 60;
return 'hsl(' + h + ', ' + s + '%, ' + l + '%)';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
An alternative is this function, but it just returns an ugly brown in the middle:
for(i=0;i<=100;i+=10) {
$('body').append('<div style="color:' + color(i/100) + '">Quality ' + i + '</div>');
}
function color(quality) {
var r = 255 * (1 - quality);
var g = 255 * quality;
var b = 0;
return 'rgb(' + r + ', ' + g + ', ' + b + ')';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Here's a possible way to do it using hue-rotate, but it has the dirty-brown problem rather. I'm including it because it uses a css filter rather than a css color, which might be of interest because it's a different approach. The constant 1.2 was reached by trial and error rather than calculation, so could be adjusted to taste.
for(i=0;i<=100;i+=10) {
$('body').append('<div style="color:hsl(355, 130%, 60%); filter:hue-rotate(' + i * 1.2 + 'deg)">Quality ' + i + '</div>');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I have modified my function to reduce the l-value the closer it gets to .5 (yellow). Obviously, the yellow is a bit muddy now, but overall I feel this is a good solution :)
for(i=0;i<=100;i+=10) {
$('body').append('<div style="color:' + color(i/100) + '">Quality ' + i + '</div>');
}
function color(quality) {
var h = 355 + 125 * quality;
var s = 130 - 60 * quality;
var l = 45 + Math.abs(0.5 - quality) * 30;
return 'hsl(' + h + ', ' + s + '%, ' + l + '%)';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I like to make the aircraft follow the path. But whatever I have tried there is a shift between the transition path and the actual drawn path on the screen. Please look at the jsfiddle
d3.selectAll('.aircraft').transition()
.duration(7500)
.attrTween('transform', translateAlong(d3.select('#samplePath').node()))
function translateAlong(path) {
let l = path.getTotalLength()
// debugger
return function (i) {
return function (t) {
let p = path.getPointAtLength(t * l)
console.log(p.x, p.y)
return 'matrix(-0.359863 -0.230143 0.230143 -0.359863' + p.x + ' ' + p.y + ')'
}
}
}
You are missing a space after the second -0.359863 in the matrix definition:
return 'matrix(-0.359863 -0.230143 0.230143 -0.359863 ' + p.x + ' ' + p.y + ')'
The penultimate line gives an "Invalid argument" error in IE11 - other browsers are fine run the code fine.
var active = $('.interactivemap-minimap-active', this.el);
if (x === undefined) x = self.x;
if (y === undefined) y = self.y;
var width = Math.round(self.container.width() / self.contentWidth / self.scale * this.el.width()),
height = Math.round(self.container.height() / self.contentHeight / self.scale * this.el.height()),
top = Math.round(-y / self.contentHeight / self.scale * this.el.height()),
left = Math.round(-x / self.contentWidth / self.scale * this.el.width()),
right = left + width,
bottom = top + height;
console.log("pass2: width=" + width + ", height=" + height + ", top=" + top + ", left=" + left + ", right=" + right + ", bottom=" + bottom);
active.each(function() {
$(this)[0].style.clip = 'rect(' + top + 'px, ' + right + 'px, ' + bottom + 'px, ' + left + 'px)';
});
the console.log will show:
width=Nan , height=Nan , top=Nan , left=Nan , right=Nan , bottom=Nan
If I comment out the troublesome line, the console.log will show:
width=Nan , height=Nan , top=Nan , left=Nan , right=Nan , bottom=Nan
width=140 , height=162 , top=-1 , left=0 , right=140 , bottom=161
So it looks like it takes a moment to populate those variables with actual data but it starts the last function when the are still equal to Nan and then errors.
Is there any way to get around this?
I'm trying to scale my SVG with g.animate({ transform: "s2.5,2.5," + bbox.cx + "," + bbox.cy }, 0); and then animate wheelAnimation(bbox.cx, bbox.cy, 1500);
var i = 0;
function wheelAnimation(cx, cy, speed){
i++;
g.animate(
{ transform: "r360," + cx + ',' + cy}, // Basic rotation around a point. No frills.
speed, // Nice slow turning rays
function(){
if(i == 5)
speed = 5000;
g.attr({ transform: 'rotate(0 ' + cx + ' ' + cy}); // Reset the position of the rays.
wheelAnimation(cx,cy, speed); // Repeat this animation so it appears infinite.
}
);
}
But my SVG didn't scaling. It's only rotates. If I remove rotation - SVG scaling. How to combine it to immediately scale and then animate rotation?
Plunker example
I've never used Snap.svg but you might try this:
var i = 0;
function wheelAnimation(cx, cy, speed, scale){
i++;
g.attr({ transform: "r0 " + cx + " " + cy + " s" + scale + "," + scale + "," + cx + "," + cy }); //Reset + Scale setup
g.animate({
transform: "r360," + cx + "," + cy + " s" + scale + "," + scale + "," + cx + "," + cy }, // Basic rotation around a point. No frills.
speed, // Nice slow turning rays
function(){
if(i == 5)
speed = 5000;
wheelAnimation(cx, cy, speed, scale); // Repeat this animation so it appears infinite.
}
);
}
Hope this helps you :)
See Plunkr
I am using d3.behaviour.zoom on my graph and all is well on the desktop. However, when i do it on an ipad the zooming is very choppy. Is there a way I can smooth it out? It seems like i need to cancel translate calls when zooming.
I have tried a couple of things but none to any great success.
an example is in the zoom handler i get the 2 touch point x and y then get the distance between then and depending if it shrinks or grows from the last stored distance i added or subtracted 0.05 to the d3.event.scale which is used in the transform.
Is this right or am i way off and need to take something else into consideration
Help much appreciated
Cheers
Mark
EDIT:
Thought i would include some code here seeing as i cannot use another service via works network
attaching the zoom handler
RadarDraw.ZoomListener = d3.behavior.zoom().scaleExtent([1, 5]).on("zoom", zoom);
// Create the SVG element, transforming the coordinates so (0,0) is at the centre
svg = d3.select("#radarContainer").append("svg")
.attr("viewBox", "0 0 " + _config.Width + " " + _config.Height + "")
.attr("id", "chartsvg")
.attr("width", _config.Width)
.attr("height", _config.Height)
//.call(zoom)
.append("g")
.attr("id", "svgGElm")
.attr("transform", "translate(" + _config.Width / 2 + "," + ((_config.Height - _config.Voffset) / 2 + _config.Voffset) + ")")
.call(_config.RadarType != "dash" ? RadarDraw.ZoomListener : function () { });
This is the zoom handler
function zoom(){
if (d3.event.scale <= 1 || d3.event.scale >= 5) {
if (!tools.IsArcFocused) {
svg.attr("transform", "translate("
+ (d3.event.translate[0] + (_config.Width / 2)) + "," + (d3.event.translate[1] + (_config.Height / 2))
+ ")scale(" + d3.event.scale + ")");
}
//handle the zoom of arc that occurs from dblclick. this moves to arc and centers it and zooms in into it.
if (tools.IsArcFocused) {
svg.attr("transform", "translate(" + _config.Width / 2 + "," + _config.Height / 2 + ")scale(" + 5 + ")translate("
+ -RadarDraw.transPosX + "," + -RadarDraw.transPosY + ")");
}
debugInfoBar(" scale: " + d3.event.scale
+ " last distance: " + lastDistance
+ " current distance: " + currentDistance);
//detect mouse wheel
if(d3.event.sourceEvent != null)
{
if (d3.event.sourceEvent.type=='mousewheel' || d3.event.sourceEvent.type=='wheel' || d3.event.sourceEvent.type=='DOMMouseScroll')
{
//if we are zoom in on an arc only listen to zoom out command to exit the zoom
//if wheeldelta is forward and in focus mode then ignore it
if (d3.event.sourceEvent.wheelDelta > 0 && tools.IsArcFocused)
{
//make sure dots stay smallest zoomed in size
scaleDots(5);
return;
}
if (d3.event.sourceEvent.wheelDelta < 0) {
if ((tools.IsArcFocused && d3.event.scale >= 5) || (tools.IsArcFocused && d3.event.scale <= 1)) {
tools.ExitZoom(false);
}
//reset to 0,0 scale 1 as we want to zoom out fully
if (d3.event.scale <= 1 && tools.IsArcFocused)
{
svg.attr("transform", "translate(" + _config.Width / 2 + "," + _config.Height / 2 + ")scale(" + 1 + ")");
}
}
}
if (d3.event.sourceEvent.type == "mousemove" && tools.IsArcFocused)
{
d3.event.scale = 5;
scaleDots(5);
return;
}
if (d3.event.sourceEvent.type == "touchmove")
{
//need to handle zoom out via touch (like whats done with mouse wheel)
return;
}
//deal with stopping double tap and double click events via the zoom
if (d3.event.sourceEvent.type == 'dblclick' || d3.event.sourceEvent.type == 'touchstart')
{
if (typeof (d3.event.preventDefault) == "function") {
d3.event.preventDefault();
d3.event.stopPropagation();
}
return;
}
}
scaleDots(d3.event.scale);
if(tools.IsArcFocused)
previousZoomLevel = 5;
else
previousZoomLevel = 1;
return;
} else {
if (tools.IsArcFocused) {
svg.attr("transform", "translate(" + _config.Width / 2 + "," + _config.Height / 2 + ")scale(" + 5 + ")translate("
+ -RadarDraw.transPosX + "," + -RadarDraw.transPosY + ")");
}
inTouchZoom = false;
//detect forward scroll when zoomed in so it exits zoom
if(d3.event.sourceEvent != null)
{
if (d3.event.sourceEvent.type == 'mousewheel' || d3.event.sourceEvent.type == 'wheel' || d3.event.sourceEvent.type == 'DOMMouseScroll') {
if (d3.event.sourceEvent.wheelDelta == 120)
{
d3.event.scale += 0.05;
if (d3.event.scale > 4.9)
d3.event.scale = 4.9;
}
else if (d3.event.sourceEvent.wheelDelta -= 120)
{
d3.event.scale -= 0.05;
if (d3.event.scale < 1)
d3.event.scale = 1;
}
//previousZoomLevel -gets confused sometimes and is 1 when it should be >=5
if (tools.IsArcFocused && previousZoomLevel >= 5) {
tools.ExitZoom(false);
previousZoomLevel = d3.event.scale;
}
}
if (d3.event.sourceEvent.type == "touchmove") {
//if only one touch point then do translation for pan otherwise leave as is
if (d3.event.sourceEvent.touches.length > 1) {
inTouchZoom = true;
//dont update translate use what was take before
d3.event.translate = touchZoomTranslate;
//we have atleast 2 points so use the first 2
var currentDistance = PointDistance(d3.event.sourceEvent.touches[0].pageX, d3.event.sourceEvent.touches[0].pageY, d3.event.sourceEvent.touches[1].pageX, d3.event.sourceEvent.touches[1].pageY);
debugInfoBar(" scale: " + d3.event.scale
+ " tp1 X: " + d3.event.sourceEvent.touches[0].pageX + " tp1Y: " + d3.event.sourceEvent.touches[0].pageY
+ " tp2 X: " + d3.event.sourceEvent.touches[1].pageX + " tp2Y: " + d3.event.sourceEvent.touches[1].pageY
+ " last distance: " + lastDistance
+ " current distance: " + currentDistance);
if (currentDistance > lastDistance) {
d3.event.scale += 0.05;
if(d3.event.scale > 4.9)
d3.event.scale = 4.9;
lastDistance = currentDistance;
}
else {
d3.event.scale -= 0.05;
if (d3.event.scale < 1)
d3.event.scale = 1;
lastDistance = currentDistance;
}
svg.attr("transform", "translate(" + _config.Width / 2 + "," + _config.Height / 2 + ")scale(" + d3.event.scale + ")");
return;
}
}
if (d3.event.sourceEvent.type == "touchend") {
//wipe last distance and we have finished touch
lastDistance = 0;
}
//deal with stopping double tap and double click events via the zoom
if (d3.event.sourceEvent.type == 'dblclick' || (d3.event.sourceEvent.type == 'touchstart' && !inTouchZoom))
{
if (typeof (d3.event.preventDefault) == "function") {
d3.event.preventDefault();
d3.event.stopPropagation();
}
return;
}
}
if (!tools.IsArcFocused) {
svg.attr("transform", "translate("
+ (d3.event.translate[0] + (_config.Width / 2)) + "," + (d3.event.translate[1] + (_config.Height / 2))
+ ")scale(" + d3.event.scale + ")");
}
if (d3.event.scale > 1)
previousZoom = true;
//if we are zoomed in and our previous zoom level is 5 or above then exit zoom (we are either scrolling out or pinching out of zoom)
if (tools.IsArcFocused && previousZoomLevel >= 5) {
tools.ExitZoom(false);
}
scaleDots(d3.event.scale);
}
if (d3.event.scale > 4.9)
d3.event.scale = 4.9;
if(!inTouchZoom)
touchZoomTranslate = d3.event.translate;
}
I corrected this issue by rewriting the zoom handler so it had specific touch input update, where I deal with zooming and not relying on d3. I also listened for a touchend event in zoom but it did not fire I had to attach another handler for zoomend and detect it that way
The zooming can be achieved without all the calculations.
var onZoom = function() {
var scale = d3.event.scale,
translate = d3.event.translate;
elemToZoom.attr('transform', 'scale(' + scale + ')translate(' + translate + ')');
};
var zoom = d3.behavior.zoom().on('zoom', onZoom);
var elemToZoom = d3.select('#zoomable')
.call(zoom);
When you are zooming in the graph, you may have to redraw the axes and the vertical and horizontal lines so that they don't get zoomed too.