d3 v4 force graph doesn't stop moving after applying links - d3.js

I have this code that is working great except that the only way I can figure out to stop making it spin is to turn up the velocityDecay, which makes the animation the point when a new node is introduced to be quite slow -- and doesn't actually stabilize the animation -- it's always moving slightly.
Here's the relevant parts of the code (or tell me if there are other important components):
```
60 let simulation = d3.forceSimulation()
61 .force('link', d3.forceLink()
62 .id((d) => { return d.index + 1 })
63 .distance(200)
64 .strength(1))
65 .force('charge', d3.forceManyBody())
66 .force('x', d3.forceX())
67 .force('y', d3.forceY())
68 .alphaTarget(1)
69 .on('tick', ticked)
70 .force('center', d3.forceCenter(window.innerWidth/2, window.innerHeight/2))
71 .force('collide', d3.forceCollide( (d) => { return 150 }).iterations(16))
72
73 svg.append('g').attr('class', 'links')
74
75 let link = svg.select('.links')
76 .selectAll('.link')
77 .data(links)
78 .enter().append('path')
79 .attr('class', 'link')
80 .attr('fill', 'transparent')
81 .attr('stroke', 'black')
82 .attr('stroke-width', '10px')
83 .exit()
84 .remove()
107 link = link.data(links)
108 .enter().append('path')
109 .attr('class', (d) => {
110 return 'link link-' + d.source + '-' + d.target
111 })
112 .attr('fill', 'transparent')
113 .attr('stroke', 'black')
114 .attr('stroke-width', '10px')
115 .merge(link)
116 link.exit().remove()
147 simulation.nodes(nodes)
148 .on('tick', ticked)
149 simulation
150 .force('charge', d3.forceManyBody())
151 // .force('center', d3.forceCenter(window.innerWidth/2, window.innerHeight/2))
152 // .force('collide', d3.forceCollide( (d) => { return 150 }).iterations(156))
153 // .alphaTarget(1)
154 .alphaDecay(.5)
155 // .alpha(1)
156 // .velocityDecay(.95)
157 .force('link', d3.forceLink()
158 .id((d) => { return d.index + 1})
159 .distance(200)
160 .iterations(20)
161 .strength(1))
162 .force('x', d3.forceX())
163 .force('y', d3.forceY())
164 .force('link').links(links)
165 simulation.restart()
```

Related

d3v4 Nesting force graphs

I have a force directed graph in d3v4 and I'd like to situate another, smaller force graph around each node.
Here is an example of what I want to do, but this is in v3. I basically tried to take this pattern from there, and it didn't work. http://bl.ocks.org/djjupa/5655723
I thought to accomplish that by creating a new one inside node.each, but that doesn't appear to be working.
Here's my code to make the new node -- it appears to be the same as the code that is successfully instantiating the first forcegraph, but this is in a d3.each function on the d3 node group.
When I inspect the childnodes by console.logging them in the tick function, I see that it has a single element array _groups that has a 3 element array with 3 undefined elements in it. Hmmm - could that be the problem?
135 console.log('instantiateChildForceGraph', parent, ix)
136
137 let subFg = d3.select(this)
138
139 parent.tokens.fixed = true
140 parent.tokens.x = 0
141 parent.tokens.y = 0
142
143 let icon_size = 16
144
145 let childNodes = parent.tokens.children
146
147 let childSimulation = d3.forceSimulation()
148 .force('collide', d3.forceCollide( (d) => { return 150 }).iterations(16))
149 .force('center', d3.forceCenter(window.innerWidth/2, window.innerHeight/2))
150 .force('link', d3.forceLink()
151 .id((d) => { return d.index + 1 })
152 .distance(200)
153 .strength(1))
154 .force('charge', d3.forceManyBody())
155 .force('x', d3.forceX())
156 .force('y', d3.forceY())
157 .alphaTarget(1)
158
159 let childNode = subFg.selectAll('.token')
160 .data(childNodes, (d) => { return d.id })
161
162 let childNodeEnter = childNode
163 .enter()
164 .append('g')
165 .attr('class', 'token-node-' + parent.id )
166 .attr('transform', (d) => { return 'translate(' + d.x + ',' + d.y + ')' })
167
168 childNodeEnter
169 .append('circle')
170 .attr('class', (d) => { return 'token token-' + d.source })
171 .attr('r', 5)
172 .style('fill', 'black')
173 .style('stroke', 'black')
174
175 childNode.exit().remove()
176
177 // let childNode = subFg.select('g.token-node-' + parent.id)
178 // .selectAll('.token')
179 // .data(childNodes, (d) => { return d.id })
180 // .enter()
181 // .attr('transform', (d) => { console.log('d', d); return 'translate(' + d.x ? d.x : 0 + ',' + d.y ? d.y : 0 + ')' })
182 // .exit()
183 // .remove()
184
185 console.log('childSimulation', childSimulation)
186 console.log('childNodes', childNodes)
187
188 console.log('no')
189 childSimulation.nodes(childNodes)
190 childSimulation.force('link').links()
191 childSimulation.on('tick', function(d) {
192 console.log('childnode', childNode)
193 childNode.attr('transform', (d) => { return 'translate(' + d.x + ',' + d.y + ')' })
194 })
195 }
196
So upon inspecting childNode I saw this particular code was actually returning 3 elements of undefined. So I had to refine the selection process for childNode to select the root <g> element of these sub nodes.
But that wasn't the only problem - I did that quickly after making these posts as a matter of fact.
But there was another problem that was much more elusive. Everything was working out, but I couldn't see the sub force animation in the browser.
It turns out that is because, probably somehow since it was a nested force graph, it was animating it with an offset of about 700 pixels, so I simply couldn't see it. That was solved by simply changing the transform function to negate the distance of the offset.

Using d3.filter in an update function

I am drawing a bar chart based on data for two different regions "lombardy" and "emiglia".
Below is an outtake of my code. First I draw the chart, by filtering on the region "lombardy". Now I would like to update the bars via a transition to use "d.value" from "emiglia".
g.select(".gdp").selectAll(".gdp-rect")
.data(data)
.enter()
.append("rect")
.classed("gdp-rect", true)
.filter(function(d) {return (d.type == "gdp") })
.filter(function(d) {return (d.region == "lombardy") })
.attr('x', function(d, i) {
return i * (width / 4)
})
.attr('y', function(d) {
return h - yBarScale(d.value)
})
.attr('width', width / 4 - barPadding)
.attr('height', function(d) {
return yBarScale(d.value)
})
.attr("fill", "#CCC")
.attr("opacity", 1);
function emiglia() {
g.selectAll(".gdp-rect")
.transition()
.duration(600)
.filter(function(d) {return (d.region == "gdp") })
.filter(function(d) {return (d.region == "emiglia") })
.attr('y', function(d) {
return h - yBarScale(d.value)
})
.attr('height', function(d) {
return yBarScale(d.value)
})
}
Is it possible to update based on d3.filter? How can I toggle d.value for both regions?
data.tsv
type region year value
gdp lombardy 2004 70
gdp lombardy 2008 50
gdp lombardy 2012 30
gdp lombardy 2016 110
gdp emiglia 2004 10
gdp emiglia 2008 15
gdp emiglia 2012 23
gdp emiglia 2016 70
There are several ways to do that, this is one of them:
To use filter in your update function, you have first to load the data...
d3.csv("data.csv", function(data){
...in an array called data. Then, inside d3.csv, you create your update function (here I'll call it draw) having the region as an argument, and filtering data based on this argument:
function draw(myRegion){
var newData = data.filter(function(d){ return d.region == myRegion})
Now you use this new array (newData) to draw your bars.
This is an example using buttons to call the function draw: https://plnkr.co/edit/QCt1XgWxrSM8MlFijyxb?p=preview
(Caveat: in this example I'm using D3 v4.x, but I see that you're using D3 v3. So, this is just an example for you to see the general idea.)
Just a final tip: normally, we don't filter the data to change a visualization like this. The normal approach, let's call it the D3 way is simply creating your dataset with both Lombardy and Emiglia as columns:
type year lombardy emiglia
gdp 2004 70 10
gdp 2008 50 15
gdp 2012 30 23
gdp 2016 110 70
That way, we could simply set the width of the bars using:
.attr("width", function(d){ return xScale(d[region])});
And setting region according to the column (Lombardy or Emiglia).

imcrop into 1 variable

I have made a a group of new pictures using imcrop from the same file, With this code, I know it's long but since the distances are not always the same I find no other way to do it than this:
A001=imcrop(A,[65 159 95 332]);
A002=imcrop(A,[182 161 95 332]);
A003=imcrop(A,[297 164 95 332]);
A004=imcrop(A,[402 165 90 332]);
A005=imcrop(A,[495 168 90 332]);
A006=imcrop(A,[606 166 90 332]);
A007=imcrop(A,[705 171 90 332]);
A008=imcrop(A,[808 175 90 332]);
A009=imcrop(A,[922 175 90 332]);
A0010=imcrop(A,[1031 175 90 332]);
Then I have a series of tasks to be performed on each of the new images, how do i get around that the easiest way? When I import multiple jpegs from a folder I can get it to make a dataset of the files but when I try to do the same with A001:A0010 I get nothing.
This is the task that I want to perform:
greenChannel = A(:, :, 2);
BW = edge(greenChannel,'Prewitt');
figure, imshow(BW)
%Dialate Lines
se90 = strel('line', 3, 90);
se0 = strel('line', 3, 0);
BWsdil = imdilate(BW, [se90 se0]);;
figure, imshow(BWsdil), title('dilated gradient mask');
%Fill Lines
BWdfill = imfill(BWsdil, 'holes');
figure, imshow(BWdfill);
title('binary image with filled holes');
BWnobord = imclearborder(BWdfill, 4);
figure, imshow(BWnobord), title('cleared border image');
seD = strel('diamond',1);
BWfinal = imerode(BWnobord,seD);
BWfinal = imerode(BWfinal,seD);
figure, imshow(BWfinal), title('segmented image');
L = bwlabel(BWfinal);
s = regionprops(L,'centroid');
What I need help to do is somehow get A001:A0010 into A in the top and run that sequence of commands, hope someone can help me achieve that!
This is hairy, but here goes:
A = imread('peppers.png');
A = imresize(A, [1500 1500]); % to handle the indexing range.
A001=imcrop(A,[65 159 95 332]);
A002=imcrop(A,[182 161 95 332]);
A003=imcrop(A,[297 164 95 332]);
A004=imcrop(A,[402 165 90 332]);
A005=imcrop(A,[495 168 90 332]);
A006=imcrop(A,[606 166 90 332]);
A007=imcrop(A,[705 171 90 332]);
A008=imcrop(A,[808 175 90 332]);
A009=imcrop(A,[922 175 90 332]);
A0010=imcrop(A,[1031 175 90 332]);
w = who; % returns the names of all your current variables in a cell.
for i = 1:numel(w)
% A00 is unique to all the variables you want to process.
if ~isempty(strfind(w{i}, 'A00'))
% hard coding greenChannel and extracting the second plane.
eval(['greenChannel = ',w{i},'(:,:,2)']);
% do the rest of the processing here,
% from BW = edge ... to regionprops.
% You may have to save the s structure as a cell array.
end
end
This uses the who command to extract all the current variables, and the eval command to evaluate what is passed in as text, based on the variable names. Note that using eval is dangerous, and should be done only if there are no better alternatives. See Use and implications of eval('expression') in MATLAB code?

d3.js - Time scale with ticks in milliseconds

I need to plot a line graph where x-axis will have ticks representing time with milliseconds detail.
For x-scale, I am using d3.time.scale()
var xScale = d3.time.scale()
.range([0, width])
x-axis looks like:
var xAxis = d3.svg.axis()
.scale(xScale)
//.ticks(d3.time.second, 1)
.orient("bottom")
.tickFormat(d3.time.format("%H:%M %L"));
But values/ticks on x-axis are not generating as expected.
data for x-axis are date objects and they hold following values(sample data)
13:25:6 794 (%H:%M%S %L)
13:25:6 898
13:25:6 994
13:25:7 95
13:25:7 194
13:25:7 295
13:25:7 395
13:25:7 495
13:25:7 595
13:25:7 710
13:25:7 795
13:25:7 895
13:25:7 995
13:25:8 95
13:25:8 195
13:25:8 294
13:25:8 395
13:25:8 495
13:25:8 594
13:25:8 795
However if I take linear scale d3.scale.linear()
Ticks generated follow a expected series.
whats the correct way of using time scale with data having millisecond details.
How can I have tick intervals in seconds:milliseconds?
EDIT:
Also, how can I have ticks every few milliseconds say every 500 ms?
there is an API d3.time.second but nothing like d3.time.millisecond. How can I add one?
Fiddle using time scale
intervals in seconds:milliseconds means, you can try out this
.tickFormat(d3.time.format("%S %L"));
(Removed outdated previous answer)
I created an issue for this against the d3 project and it has been fixed: https://github.com/mbostock/d3/issues/1529
An example can be seen here: http://bl.ocks.org/mbostock/6618724

How to speed up matplotlib scatter ploting?

I need to draw a batch of scatter charts in matplotlib, and found the speed of matplotlib is slow, then I lineprofile the function, and found the hotspot is fig, ax = plt.subplots(), It costs 56.1% of time to creat a blank figure and axes !!
How to speed it up ? I mean, how can I reuse fig and ax to avoid creating them each time ?
Attach the profile report here (I cut some of the line to make it simple)
Total time: 0.733771 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
517 #profile
518 def df_scatter(df):
519 ''' draw the scatter plot for Pandas dataframe 'df'
533 '''
536
537 1 75 75.0 0.0 biggest_area = 1000
538 1 117 117.0 0.0 mycm = matplotlib.cm.get_cmap('jet') # 'spectral'
539
541 1 78 78.0 0.0 col_qty = len(df.columns)
543
544 1 1859 1859.0 0.1 x = list(df.ix[:,0].values)
545 1 1258 1258.0 0.0 y = list(df.ix[:,1].values)
551
552 1 1472345 1472345.0 56.1 fig, ax = plt.subplots()
556
557 1 7007 7007.0 0.3 plt.subplots_adjust(left=0.07, right=0.92, bottom=0.1, top=0.95)
558 1 179 179.0 0.0 x_margin, y_margin = (max(x)-min(x))/20, (max(y)-min(y))/20
563
564 1 71 71.0 0.0 if col_qty > 2:
565 1 1602 1602.0 0.1 r = list(df.ix[:,2].values)
566 1 309 309.0 0.0 size_r = np.array(biggest_area)*r/max(r)
585
586 1 34712 34712.0 1.3 sc = plt.scatter(x, y, marker='o', s=size_r, cmap=mycm, alpha=0.65)
587
588 # adding colorbar
589 1 542417 542417.0 20.7 cbaxes = fig.add_axes([0.94, 0.25, 0.02, 0.70])
590 1 165719 165719.0 6.3 cbar = plt.colorbar(sc, cax=cbaxes)
591 1 122 122.0 0.0 cbar.solids.set_edgecolor('face')
595
602 1 1061 1061.0 0.0 plt.figtext(0.94,0.10,"%0.1f"%(max(r)), fontproperties=TEXT_FONT_MEDIUM)
639 1 66 66.0 0.0 return fig
I think that the best way to do it is calling
fig = plt.figure()
ax=fig.add_subplot(111)
from outside of df_scatter. Then, pass it to df_scatter as arguments:
df_scatter(df,fig,ax):
or simply do inside df_scatter:
def df_scatter(df):
fig = plt.gcf()
ax = plt.gca()
after the creation of fig & axis was done.

Resources