Advertisement
Guest User

Untitled

a guest
May 2nd, 2019
230
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.24 KB | None | 0 0
  1. var USER_SPEED = "slow";
  2.  
  3. var speeds = { "slow": 1000, "medium": 400, "fast": 100 };
  4. var simtimer;
  5.  
  6.  
  7. var margin = {top: 90, right: 0, bottom: 0, left: 170};
  8. var width = parseInt(d3.select("#chart").style('width'), 10);
  9.  
  10. var maxRadius = width < 500 ? 3 : 4.5,
  11. height = (width < 500 ? 400 : 660) - margin.top - margin.bottom;
  12. margin.left = width < 500 ? 60 : 160;
  13. width = width - margin.left - margin.right;
  14. var padding = 2;
  15.  
  16. var sched_objs = [],
  17. curr_index = -1;
  18.  
  19. // this has a count for how many are in each group
  20. var grpcnts = {
  21. met: { '1960': 0, '1970': 0, '2010': 0, label: "First Met", x: 1, y: 1 },
  22. romantic: { '1960': 0, '1970': 0, '2010': 0, label: "Romantic", x: 2, y: 1 },
  23. lived: { '1960': 0, '1970': 0, '2010': 0, label: "Live Together", x: 3, y: 1 },
  24. married: { '1960': 0, '1970': 0, '2010': 0, label: "Married", x: 4, y: 3 },
  25. }
  26.  
  27. var x = d3.scaleBand()
  28. .domain([1950, 1970, 1980, 2010])
  29. .range([0, width]);
  30.  
  31. var y = d3.scaleBand()
  32. .domain(d3.keys(grpcnts))
  33. .range([height, 0]);
  34.  
  35. // Start the SVG
  36. var svg = d3.select("#chart").append("svg")
  37. .attr("width", width + margin.left + margin.right)
  38. .attr("height", height + margin.top + margin.bottom)
  39. .append("g")
  40. .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  41. // consol
  42.  
  43. // Load data and let's do it.
  44. d3.tsv("timelines.tsv")
  45. .then(function(data) {
  46.  
  47. data.forEach(function(d) {
  48. var day_array = d.timeline.split(",");
  49. var activities = [];
  50. for (var i=0; i < day_array.length; i++) {
  51. // Duration
  52. if (i % 2 == 1) {
  53. activities.push({'act': day_array[i-1], 'duration': +day_array[i]});
  54. }
  55. }
  56. sched_objs.push(activities);
  57. });
  58.  
  59. // A node for each person's schedule
  60. var nodes = sched_objs.map(function(o,i) {
  61. var act = o[0].act;
  62. var init_x = x(+data[i].decade) + Math.random();
  63. var init_y = y('met') + Math.random();
  64. var col = "#cccccc";
  65. grpcnts[act][data[i].decade] += 1;
  66.  
  67. return {
  68. act: act,
  69. radius: maxRadius,
  70. x: init_x,
  71. y: init_y,
  72. decade: data[i].decade,
  73. color: color(act),
  74. married: false,
  75. moves: 0,
  76. next_move_time: o[0].duration,
  77. sched: o
  78. }
  79. });
  80.  
  81. // Separator line.
  82. svg.append("line")
  83. .attr("class", "separator")
  84. .attr("x1", (x("2010")+x("1970")) / 2)
  85. .attr("x2", (x("2010")+x("1970")) / 2)
  86. .attr("y1", -margin.top+20)
  87. .attr("y2", height);
  88.  
  89. var force = d3.forceSimulation()
  90. .velocityDecay(.9)
  91. .force("charge", d3.forceManyBody().strength())
  92. .on("tick", tick)
  93. .nodes([{},{}]);
  94.  
  95. var circle = svg.selectAll("circle")
  96. .data(nodes)
  97. .enter().append("circle")
  98. .style("fill", function(d) { return d.color; });
  99.  
  100. circle.transition()
  101. .duration(500)
  102. .delay(function(d,i) { return i * 5; })
  103. .attrTween("r", function(d) {
  104. var i = d3.interpolate(0, d.radius);
  105. return function(t) { return d.radius = i(t); };
  106. })
  107.  
  108. var yearlabel = svg.selectAll("text.decade")
  109. .data([1970, 2010])
  110. .enter().append("text")
  111. .attr('class', 'decade')
  112. .attr('x', d => x(d))
  113. .attr('y', -margin.top)
  114. .attr('dy', '1.8em')
  115. .attr("text-anchor", "middle")
  116. .text(d => d + "s");
  117.  
  118. // Activity group labels
  119. var actlabel = svg.selectAll("text.actlabel")
  120. .data(d3.keys(grpcnts))
  121. .enter().append("text")
  122. .attr("class", "actlabel")
  123. .attr("text-anchor", "left")
  124. .attr("y", d => y(d))
  125. .attr("x", -margin.left)
  126. .text(d => grpcnts[d].label)
  127.  
  128. // Counter labels
  129. var fcntlabel = svg.selectAll(".fcntlabel")
  130. .data(d3.keys(grpcnts))
  131. .enter().append("g")
  132. .attr("class", "fcntlabel")
  133. .attr("transform", d => "translate("+(x('1970'))+","+y(d)+")");
  134.  
  135. fcntlabel.append("text")
  136. .attr("class", "cnt")
  137. .attr("text-anchor", "middle")
  138. .text(d => d == 'met' ? "100%" : "0%");
  139.  
  140. // 2010s
  141. var mcntlabel = svg.selectAll(".mcntlabel")
  142. .data(d3.keys(grpcnts))
  143. .enter().append("g")
  144. .attr("class", "mcntlabel")
  145. .attr("transform", d => "translate("+(x('2010'))+","+y(d)+")");
  146.  
  147. mcntlabel.append("text")
  148. .attr("class", "cnt")
  149. .attr("text-anchor", "middle")
  150. .text(d => d == 'met' ? "100%" : "0%");
  151.  
  152. var pymChild = new pym.Child();
  153.  
  154. // Update nodes based on activity and duration
  155. function timer() {
  156.  
  157. if (curr_index < 0) {
  158. fcntlabel.selectAll("text.cnt").text(d => d == "met" ? "100%" : "0%");
  159. mcntlabel.selectAll("text.cnt").text(d => d == "met" ? "100%" : "0%");
  160. }
  161.  
  162. else {
  163.  
  164. d3.range(nodes.length).map(function(i) {
  165.  
  166. // Time to go to next activity
  167. if (nodes[i].next_move_time == curr_index) {
  168. if (nodes[i].moves == nodes[i].sched.length-1) {
  169. nodes[i].moves = 0;
  170. } else {
  171. nodes[i].moves += 1;
  172. }
  173.  
  174. // Subtract from current group count
  175. grpcnts[ nodes[i].act ][nodes[i].decade] -= 1;
  176.  
  177. // Move on to next activity
  178. nodes[i].act = nodes[i].sched[ nodes[i].moves ].act;
  179.  
  180. // Add to new group count
  181. grpcnts[ nodes[i].act ][nodes[i].decade] += 1;
  182.  
  183. nodes[i].next_move_time += nodes[i].sched[ nodes[i].moves ].duration + 1;
  184. }
  185.  
  186. });
  187.  
  188. fcntlabel.selectAll("text.cnt")
  189. .text(d => readablePercent(grpcnts[d]['1970'], 250));
  190. mcntlabel.selectAll("text.cnt")
  191. .text(d => readablePercent(grpcnts[d]['2010'], 250));
  192.  
  193. }
  194.  
  195. force.restart()
  196. curr_index += 1;
  197.  
  198. d3.select("#current_time").text(indexToTime(curr_index));
  199.  
  200. if (grpcnts['married']['1970'] == 250 && grpcnts['married']['2010'] == 250) {
  201. setTimeout(reset, 5000);
  202. }
  203.  
  204. else if (USER_SPEED != "pause") {
  205. simtimer = setTimeout(timer, speeds[USER_SPEED]);
  206. }
  207. }
  208.  
  209. simtimer = setTimeout(timer, 5000);
  210.  
  211. function tick() {
  212. var k = 0.03 * this.alpha;
  213.  
  214. // Push nodes toward their designated focus.
  215. nodes.forEach(function(o, i) {
  216. var curr_act = o.act;
  217.  
  218. // Speed of movement change based on user speed.
  219. if (USER_SPEED == "slow") var damper = .85;
  220. else var damper = 1;
  221.  
  222.  
  223. o.x += (x(+o.decade) - o.x) * k * damper;
  224.  
  225. // If starting at the beginning.
  226. if (curr_index < 0) {
  227. o.y += (y('met') - o.y) * k * damper;
  228. o.color = color('met');
  229. }
  230.  
  231. // Already started.
  232. else {
  233. o.y += (y(curr_act) - o.y) * k * damper;
  234. o.color = color(curr_act);
  235. }
  236.  
  237. });
  238.  
  239. circle
  240. .each(collide(.5))
  241. .style("fill", function(d) { return d.color; })
  242. .attr("cx", function(d) { return d.x; })
  243. .attr("cy", function(d) { return d.y; });
  244. }
  245.  
  246.  
  247. // Resolve collisions between nodes.
  248. function collide(alpha) {
  249.  
  250. var quadtree = d3.quadtree(nodes);
  251.  
  252. return function(d) {
  253. var r = d.radius + maxRadius + padding,
  254. nx1 = d.x - r,
  255. nx2 = d.x + r,
  256. ny1 = d.y - r,
  257. ny2 = d.y + r;
  258. quadtree.visit(function(quad, x1, y1, x2, y2) {
  259.  
  260. if (quad.point && (quad.point !== d)) {
  261. var x = d.x - quad.point.x,
  262. y = d.y - quad.point.y,
  263. l = Math.sqrt(x * x + y * y),
  264. r = d.radius + quad.point.radius + (d.act !== quad.point.act) * padding;
  265. if (l < r) {
  266. l = (l - r) / l * alpha;
  267. d.x -= x *= l;
  268. d.y -= y *= l;
  269. quad.point.x += x;
  270. quad.point.y += y;
  271. }
  272. }
  273. return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
  274. });
  275. };
  276. }
  277.  
  278. // Speed toggle
  279. d3.selectAll(".togglebutton")
  280. .on("click", function() {
  281. if (d3.select(this).attr("data-val") == "pause") {
  282. d3.select(".pause").classed("current", true);
  283. d3.select(".slow").classed("current", false);
  284. d3.select(".fast").classed("current", false);
  285. } else if (d3.select(this).attr("data-val") == "slow") {
  286. d3.select(".pause").classed("current", false);
  287. d3.select(".slow").classed("current", true);
  288. d3.select(".fast").classed("current", false);
  289. } else {
  290. d3.select(".pause").classed("current", false);
  291. d3.select(".slow").classed("current", false);
  292. d3.select(".fast").classed("current", true);
  293. }
  294.  
  295. force.restart()
  296. // force.resume();
  297. clearTimeout(simtimer);
  298. USER_SPEED = d3.select(this).attr("data-val");
  299.  
  300. if (USER_SPEED != "pause") timer();
  301. });
  302.  
  303.  
  304. d3.select("#resetbutton a").on("click", function() { reset(); });
  305.  
  306. // Reset.
  307. function reset() {
  308.  
  309. console.log('reset!');
  310.  
  311. clearTimeout(simtimer);
  312. USER_SPEED = "fast";
  313.  
  314. // Reset counts.
  315. curr_index = -1;
  316. d3.select("#current_time").text(indexToTime(0));
  317. d3.keys(grpcnts).forEach(function(d,i) {
  318. grpcnts[d]['1970'] = 0;
  319. grpcnts[d]['2010'] = 0;
  320. });
  321.  
  322. // Reset the nodes.
  323. d3.range(nodes.length).map(function(i) {
  324.  
  325. nodes[i].act = nodes[i].sched[0].act;
  326. grpcnts[nodes[i].act][nodes[i].decade] += 1;
  327. // nodes[i].act = 'met';
  328. // grpcnts['met'][nodes[i].decade] += 1;
  329. nodes[i].moves = 0;
  330. nodes[i].next_move_time = nodes[i].sched[0].duration;
  331.  
  332. });
  333.  
  334. fcntlabel.selectAll("text.cnt").text(d => d == "met" ? "100%" : "0%");
  335. mcntlabel.selectAll("text.cnt").text(d => d == "met" ? "100%" : "0%");
  336.  
  337. // Restart the animation.
  338. force.restart()
  339. // force.resume();
  340. USER_SPEED = d3.select("#speed .togglebutton.current").attr("data-val");
  341.  
  342. if (USER_SPEED != 'pause') {
  343. simtimer = setTimeout(timer, 3000);
  344. } else {
  345. // force.resume();
  346. }
  347.  
  348.  
  349. }
  350.  
  351.  
  352. }) // @end d3.tsv
  353.  
  354. function color(activity) {
  355.  
  356. var colorByActivity = {
  357. 'met': '#faf88e',
  358. 'romantic': '#b8e9a4',
  359. 'lived': '#8ad3ba',
  360. 'married': '#6fb9d0',
  361. }
  362.  
  363. return colorByActivity[activity];
  364.  
  365. }
  366.  
  367. // Output readable percent based on count.
  368. function readablePercent(n, total) {
  369.  
  370. var pct = 100 * n / total;
  371. if (pct < 1 && pct > 0) {
  372. pct = "1%";
  373. } else {
  374. pct = Math.round(pct) + "%";
  375. }
  376.  
  377. return pct;
  378. }
  379.  
  380. // Minutes to time of day. Data is minutes from 4am.
  381. function minutesToTime(m) {
  382. var minutes = (m + 4*60) % 1440;
  383. var hh = Math.floor(minutes / 60);
  384. var ampm;
  385. if (hh > 12) {
  386. hh = hh - 12;
  387. ampm = "pm";
  388. } else if (hh == 12) {
  389. ampm = "pm";
  390. } else if (hh == 0) {
  391. hh = 12;
  392. ampm = "am";
  393. } else {
  394. ampm = "am";
  395. }
  396. var mm = minutes % 60;
  397. if (mm < 10) {
  398. mm = "0" + mm;
  399. }
  400.  
  401. return hh + ":" + mm + ampm
  402. }
  403.  
  404. function indexToTime(i) {
  405.  
  406. var months = i % 12;
  407. var years = Math.floor(i / 12);
  408.  
  409. var time_string = '';
  410.  
  411. if (years == 1) {
  412. time_string += years + " year";
  413. } else if (years > 1) {
  414. time_string += years + " years";
  415. }
  416.  
  417. if (years > 0) {
  418. time_string += ", ";
  419. }
  420.  
  421. if (months == 1) {
  422. time_string += "1 month";
  423. } else {
  424. time_string += months += " months";
  425. }
  426.  
  427. return time_string;
  428. }
  429.  
  430. // For SVG text-wrapping
  431. function wrap(text, width) {
  432. text.each(function() {
  433. var text = d3.select(this),
  434. words = text.text().split(/\s+/).reverse(),
  435. word,
  436. line = [],
  437. lineNumber = 0,
  438. lineHeight = 1.1, // ems
  439. x = text.attr("x"),
  440. y = text.attr("y"),
  441. dy = parseFloat(text.attr("dy")),
  442. tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
  443. while (word = words.pop()) {
  444. line.push(word);
  445. tspan.text(line.join(" "));
  446. if (tspan.node().getComputedTextLength() > width) {
  447. line.pop();
  448. tspan.text(line.join(" "));
  449. line = [word];
  450. tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
  451. }
  452. }
  453. });
  454. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement