How do I improve performance of JointJS with many links? - jointjs

After profiling the code it looks like bbox function is being called repeatedly. I cannot remove marker-source, marker-target and connection-wrap because I need those features. Is there a way to improve performance?

Try to replace marker-source and marker-target with SVG Markers. It's relatively easy to implement if your application does not require different sizes and colors of markers. For instance:
Define you own marker arrow.
var arrowMarker = V('marker', {
viewBox: "0 0 10 10",
refX: 9,
refY: 5,
markerWidth: 6,
markerHeight: 6,
orient: "auto"
}, [
V('path', {
'd': "M 0 0 L 10 5 L 0 10 z",
'fill': 'green'
})
]);
Add the marker to paper's SVG Defs so it can be reused later for multiple times.
V(paper.defs).append(arrowMarker);
Finally put the marker on a link. Use the marker-end or marker-start property to define the target resp. source marker.
var link = new joint.dia.Link({
markup: '<path class="connection"/><path class="connection-wrap"/>',
attrs: {
'.connection': {
'stroke': 'green',
'stroke-width': 2,
'marker-end': 'url(#' + arrowMarker.attr('id') + ')'
}
}
});
There is a JSFiddle with an example and other useful performance tips.

Related

Change shape of marker-arrowhead

I want to change the image of marker-arrowheads to achieve uniformity for my web application.
Link Image
Is there a simple and possible way to change the image of marker-arrowheads.
I solved the problem by following the below mentioned solution:
link.attr({
'.marker-arrowhead[end="source"]': { fill: 'red', d: 'M 10 0 L 0 5 L 10 10 z' },
'.marker-arrowhead[end="target"]': { fill: 'yellow', d: 'M 10 0 L 0 5 L 10 10 z' }
});

Draw a path out gradually from the middle

Suppose I have an arbitrary path drawn with d3
var points = [0, 4, 4, 4, 8, 8, 4, 4, 4, 8, 4, 4, 4, 4, 0];
var svg = d3.select('svg');
var line = d3.svg.line()
.y(function(d) { return 10*d})
.x(function(d, t) { return t*20 })
.interpolate('cubic');
svg.append('path')
.attr('d', line(points))
.attr('stroke', 'black')
.attr('stroke-width', 2)
.attr('fill', 'none')
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"></script>
<svg style="width: 100%; height: 100%; outline: 1px solid green;"></svg>
I want to animate the path drawing out slowly. I understand how to do it from beginning to end but in my case I want to start at an arbitrary point on the line and draw outward at the same rate in both direction.
I can't quite figure out how to do this.
I can utilize attrTween and take slices of the array varying by time, but if I do that, the line shifts since I don't know how to set the horizontal offset correctly at each step.
Hopefully you're working with the second answer on the page you linked to (the one by #duopixel), because that's a much better way. If so, then you can make the line start drawing from the middle if you slightly modify the attributes:
var points = [0, 4, 4, 4, 8, 8, 4, 4, 4, 8, 4, 4, 4, 4, 0];
var svg = d3.select('svg');
var line = d3.svg.line()
.y(function(d) { return 10*d})
.x(function(d, t) { return t*20 })
.interpolate('cubic');
var path = svg.append('path')
.attr('d', line(points))
.attr('stroke', 'black')
.attr('stroke-width', 2)
.attr('fill', 'none');
totalLength = path.node().getTotalLength();
path
.attr("stroke-dasharray", '0 ' + totalLength)
.attr("stroke-dashoffset", totalLength/2)
.transition()
.duration(2000)
.attr("stroke-dasharray", totalLength + ' 0')
.attr("stroke-dashoffset", totalLength);
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"></script>
<svg style="width: 100%; height: 100%; outline: 1px solid green;"></svg>
How it works:
First, consider the original, common scenario of animating the drawing of a path from its start to its end. If l is the length of the path (obtained by path.node().getTotalLength()) then setting the path's stroke-dasharray to l + ' ' + l makes the dash and the gap between it and the next dash both equal to l. Under these conditions, the path would appear solid, because the dash length being l "pushes" the gap past the end of the path. By setting stroke-dashoffset to l, the above scenario is reversed — the path is entirely a gap (hence invisible) and the dash falls off the path. By transitioning between these 2 states (from one with offset to one without offset), we get a gradual drawing of a line.
Now, to animate from the middle, the dash is defined with length 0 and the gap has a length l. So now the line is also completely invisible, due to the 0-length dash and the l-length gap . Setting stroke-dashoffset to l/2 moves this 0-length dash halfway down the path. That's the animation starting state. The end state is the reverse dasharray where gap is 0 and dash-length is l, which renders the line completely solid. To properly transition between these states, we also need to set the end state's stroke-dashoffset to l. That was something I figured out experimentally, and I'm not sure exactly how to explain it in words. But it works....

Flatten points into SVG polyline Polymer

I have the following data structure:
'points': [
{
'x': 5535,
'y': 5535
},
{
'x': 5535,
'y': 60000
},
{
'x': 60000,
'y': 60000
},
{
'x': 60000,
'y': 5535
}
];
I would like to flatten it to 5535,5535 5535,60000 60000,60000 60000,5535 to use as a polyline points attribute.
I have the following in Polymer <template>
<polyline
id="polygon",
points="{{points | polygonPoints | toSvgPoints(width, height)}}"
stroke="rgba({{colour.r}}, {{colour.g}}, {{colour.b}}, {{opacity}})"
fill="rgba({{colour.r}}, {{colour.g}}, {{colour.b}}, {{opacity * 0.6}})"
stroke-width="{{lineWidth}}"
stroke-linecap="round"
stroke-linejoin="round"
/>
Where polygonPoints and toSvgPoints filters look like so:
/**
* Retrieves the polygon points for this object.
* #param point optionally provide a list of normalized points
* #returns the polygon points or all points if a line
*/
polygonPoints: function(points) {
var array = points.slice(0);
points = points || this.points;
array.push(points[0])
return array;
},
/**
* Retrieves the polygon points for this object.
* #param point optionally provide a list of normalized points
* #returns the polygon points or all points if a line
*/
toSvgPoints: function(points, width, height) {
var i, string = '';
points = points || this.points;
for (i = 0; i < points.length; ++i) {
string += this.normalizedToActual(points[i].x, width);
string += ',';
string += this.normalizedToActual(points[i].y, height);
string += ' ';
}
return string;
},
This works, the toSvgPoints returns the correct points but the bindings to the point values do not get set up automagically by Polymer. For example, if I modify this.points[0].x = 4219 the polygon doesn't update because the binding hasn't been created to the polygon attribute.
Is this something that just can't be solved without providing some other method that invokes a redraw? Ideally I'd just want to do this:
<polyline
id="polygon",
points="{{x,y for (points | polygonPoints)}}"
...
/>
Which would stamp out the x and y values in the points attribute and set up the bindings.
PolymerExpressions only observes objects that are directly referenced in an expression, so in this case it observes points, but not the properties on the elements in the array. If you replace a point, the expression will update, but it won't if you modify a point.
There are a few ways to deal with this. If you know where a point is being modified, at you have a reference to the list there, you can manually notify on the list. I usually work with Polymer.Dart, but I think that the notify() function in observe-js is what you're looking for: https://github.com/Polymer/observe-js/blob/master/src/observe.js#L1076
Alternatively, instead of returning a static projection of the points, have toSvgPoints listen to all of its dependencies: points, x and y for each point, width and height. When an input changes, update the output array. This will cause a chain of updates that propagates to your view.
Use Polymer's observe-js library to do observation. It polyfills Object.observe() on platforms that don't have it. https://github.com/Polymer/observe-js#objectobserver
I don't know for sure but I guess Polymer doesn't observe individual attributes inside an array. You could try to replace the item at that position:
this.points[0] = {x:4219,y:this.points[0].y}
or alternatively create a new array and set it to this.points ?
Well that took ages. Don't use a polyline use a path:
<path
id="zone"
on-down="{{dragStart}}"
on-up="{{dragEnd}}"
d="M {{points[0].x| max(width)}} {{points[0].y | max(height)}} {{points | polygonPoints | toSvgPoints(width, height)}}"
fill="rgba({{colour.r}}, {{colour.g}}, {{colour.b}}, {{(selected ? 0.8 : 0.6) * 0.6}})"
stroke="rgba({{colour.r}}, {{colour.g}}, {{colour.b}}, {{(selected ? 0.8 : 0.6)}})"
stroke-linecap="round"
stroke-linejoin="round"
/>
The M {{points[0].x| max(width)}} {{points[0].y | max(height)}} at the start of the d attribute forces the redraw.

Flot graphing stacks from opposite axies

I'm working a display where I don't know where the end point is (100% is an evolving figure... graph will update via ajax). The data is going to a mix of completed records against an estimated count where it will complete.
As time goes on, the counts go up and the estimated completion point becomes tighter.
not sure that image ^ is working... (most things are blocked here, sorry)
ascii example as a horiz bar:
[///////40%////// ******SPACE*******/////20%//////]
I've tried using data like
var rawData = [
[40, 0],
[-20, 0],
];
to show 40% done and 20% as potential end point range. but that doesn't work. at all
var rawData = [
[40, 0],
[20, 0],
];
shows as 60% in stacked format which is not what I want.
Can I have 2 bars on the same 'row' that are stacked, but not stacked from the same axis?
As far as I know there is no way to stack a single series against itself. With that said here's my 5 min attempt to replicate your image:
As #DNS had suggested, I used a transparent middle series to space the complete/estimated bars. To keep it easier to track where the points belong, I added each bar as it's own series.
Fiddle is here.
var data = [];
// 10 passes
data.push({data:[[40,2]], color: 'green'});
data.push({data:[[40,2]], color: 'transparent'});
data.push({data:[[20,2]], color: 'silver'});
// 100 passes
data.push({data:[[50,1]], color: 'green'});
data.push({data:[[20,1]], color: 'transparent'});
data.push({data:[[30,1]], color: 'silver'});
// 1000 passes
data.push({data:[[70,0]], color: 'green'});
data.push({data:[[20,0]], color: 'transparent'});
data.push({data:[[10,0]], color: 'silver'});
$.plot($("#placeholder"), data, {
series: {
stack: true,
lines: { show:false },
bars: { show: true, horizontal:true, barWidth: 0.6 }
},
xaxis: {min: 0, max: 100, ticks: [[0, '<span style="color: green">Completed<br/>0%</span>'],[100, '<span style="color: gray">Estimated End Point<br/>100%</span>']]},
legend: {show: false},
yaxis: {tickColor: 'transparent',position: 'right',
ticks: [[0.25,'as of 1000 passes'],
[1.25,'as of 100 passes'],
[2.25,'as of 10 passes']]}
});
Create 3 bars instead of 2, with the middle bar filling the remaining space up to 100. Use series objects, so you can assign the middle one an 'empty' color, like white.
So with your example above, where you have 40 and 20, your middle bar would have a value of 100 - 40 - 20 = 40.

Multi-colored Google Map SVG Symbols

A Google Map marker can take a complex svg path as its icon as in something like:
var baseSvg = {
x1 : "m 0,0 l45,0 l 190,225 l -45,0 l -190,-225 z",
x2 : "m 225,0 l -45,0 l -190,225 l 45,0 l 190,-225 z"
};
var baseIcon = {
path: "M0,0 " + baseSvg["x1"] + baseSvg["x2"],
fillColor: "#000000",
fillOpacity: 1,
scale: .2,
strokeColor: "black",
strokeWeight: 0,
rotation: 15
};
which is then fed into a marker:
var marker = new google.maps.Marker({
position: new google.maps.LatLng(somelat, somelng),
icon: baseIcon
});
All good. But it only draws in a single colour (black in this example). So, can they only have a single colour or is there a way to have multi-coloured symbols? Using the example code, x1 would be red and x2 would be green.
Note that this construct is borrowed from here: How do I indicate transparency in an SVG path in Google Maps API v3? and it works nicely.
I just did the same thing and I think you have to draw two markers with essentially identical data, but with the path and in this case fillColor properties changed:
var baseSvg = {
x1 : "m 0,0 l45,0 l 190,225 l -45,0 l -190,-225 z",
x2 : "m 225,0 l -45,0 l -190,225 l 45,0 l 190,-225 z"
},
baseIcon = {
fillOpacity: 1,
scale: .2,
strokeColor: "black",
strokeWeight: 0,
rotation: 15
},
markerPos = new google.maps.LatLng(somelat, somelng),
// psuedo code for cloning an object since that
// is out of scope for this question
greenIcon = Object.clone(baseIcon),
redIcon = Object.clone(baseIcon);
greenIcon.path = baseSvg.x1;
greenIcon.fillColor = "#0f0";
redIcon.path = baseSvg.x2;
redIcon.fillColor = "#f00";
var marker1 = new google.maps.Marker({
position: markerPos,
map: map,
icon: greenIcon
});
var marker2 = new google.maps.Marker({
position: markerPos,
map: map,
icon: redIcon
});
obviously not optimized js, but you get the idea.
When it comes to opacity, strokeOpacity is your guy. Check the maps.Symbol class for more information on symbol properties.

Resources