Created
January 24, 2020 20:30
-
-
Save Alex-Devoid/a062f99bdeccca612433c9fc961b9cbd to your computer and use it in GitHub Desktop.
chuck, transition and remove 1000s of circles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <head> | |
| <title>transition and delete</title> | |
| <link rel="stylesheet" href="style/style.css" type="text/css" media="screen" /> | |
| <meta charset="utf-8"> | |
| </head> | |
| <body> | |
| <div id="main-wrapper"> | |
| <button type="button" name="button" onclick="transition()">transition</button> | |
| <div id="chart"></div> | |
| </div><!-- @end #main-wrapper --> | |
| <script src="//d3js.org/d3.v4.min.js"></script> | |
| <script> | |
| d3.selection.prototype.size = function() { | |
| var n = 0; | |
| this.each(function() { ++n; }); | |
| return n; | |
| }; | |
| var margin = {top: 16, right: 0, bottom: 0, left: 0}, | |
| width = 950 - margin.left - margin.right, | |
| height = 700 - margin.top - margin.bottom; | |
| var node_radius = 5, | |
| padding = 1, | |
| cluster_padding = 10, | |
| num_nodes = 200; | |
| var svg = d3.select("#chart").append("svg") | |
| .attr("width", width + margin.left + margin.right) | |
| .attr("height", height + margin.top + margin.bottom) | |
| .append("g") | |
| .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
| var mexico; | |
| var usa; | |
| var usa1; | |
| var ven; | |
| var canada; | |
| var geoMercator = d3.geoMercator() | |
| var path = d3.geoPath().projection(d3.geoMercator()); | |
| d3.json("https://gist.githubusercontent.com/Alex-Devoid/090691fe85351ebc8eefb0f21515fc2d/raw/e7f0904f2b48943bc90a3d50e5c244665fda6d97/110_admin.geojson", function(error, world) { | |
| if (error) throw error; | |
| d3.csv("https://gist.githubusercontent.com/Alex-Devoid/090691fe85351ebc8eefb0f21515fc2d/raw/e7f0904f2b48943bc90a3d50e5c244665fda6d97/mpp_courts_d3_texas.csv", function(errorData, mppData) { | |
| if (errorData) throw errorData; | |
| var lookup = {}; | |
| var sourceCountries = []; | |
| //https://stackoverflow.com/questions/17780508/selecting-distinct-values-from-a-json | |
| for (var item, i = 0; item = mppData[i++];) { | |
| var country = item.source; | |
| if (!(country in lookup)) { | |
| lookup[country] = 1; | |
| sourceCountries.push(country); | |
| } | |
| } | |
| var destinations = | |
| [{ | |
| name:'elPaso', | |
| type: "Point", | |
| coordinates: [-97.745015,26.020156], | |
| }, | |
| { | |
| name:'sanDiego', | |
| type: "Point", | |
| coordinates: [-117.035644,32.534302], | |
| }, | |
| { | |
| name:'Texas', | |
| type: "Point", | |
| coordinates: [-99.533056,27.455729], | |
| }] | |
| for (var i = 0; i < destinations.length; i++) { | |
| destinations[i].point = geoMercator(destinations[i].coordinates) | |
| } | |
| console.log(sourceCountries) | |
| var data1 = [] | |
| for (var i = 0; i < world.features.length; i++) { | |
| for (var b = 0; b < sourceCountries.length; b++) { | |
| if (world.features[i].properties.SOVEREIGNT === sourceCountries[b]){ | |
| data1.push({ | |
| "class": `${world.features[i].properties.SOVEREIGNT}_${destinations[0].name}`, | |
| "source": { | |
| "lat": path.centroid(world.features[i])[1], | |
| "lon": path.centroid(world.features[i])[0] | |
| }, | |
| "destination": { | |
| "lat": destinations[0].point[1], | |
| "lon": destinations[0].point[0] | |
| } | |
| }, | |
| { | |
| "class": `${world.features[i].properties.SOVEREIGNT}_${destinations[1].name}`, | |
| "source": { | |
| "lat": path.centroid(world.features[i])[1], | |
| "lon": path.centroid(world.features[i])[0] | |
| }, | |
| "destination": { | |
| "lat": destinations[1].point[1], | |
| "lon": destinations[1].point[0] | |
| } | |
| }, | |
| { | |
| "class": `${world.features[i].properties.SOVEREIGNT}_${destinations[2].name}`, | |
| "source": { | |
| "lat": path.centroid(world.features[i])[1], | |
| "lon": path.centroid(world.features[i])[0] | |
| }, | |
| "destination": { | |
| "lat": destinations[2].point[1], | |
| "lon": destinations[2].point[0] | |
| } | |
| } | |
| ) | |
| } | |
| } | |
| } | |
| svg.selectAll("path") | |
| .data(world.features) | |
| .enter().append("path") | |
| .attr("d", path); | |
| var projection = d3.geoMercator(); | |
| var curve = function(context) { | |
| var custom = d3.curveLinear(context); | |
| custom._context = context; | |
| custom.point = function(x,y) { | |
| x = +x, y = +y; | |
| switch (this._point) { | |
| case 0: this._point = 1; | |
| this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); | |
| this.x0 = x; this.y0 = y; | |
| break; | |
| case 1: this._point = 2; | |
| default: | |
| var x1 = this.x0 * 0.5 + x * 0.5; | |
| var y1 = this.y0 * 0.5 + y * 0.5; | |
| var m = 1/(y1 - y)/(x1 - x); | |
| var r = -100; // offset of mid point. | |
| var k = r / Math.sqrt(1 + (m*m) ); | |
| if (m == Infinity) { | |
| y1 += r; | |
| } | |
| else { | |
| y1 += k; | |
| x1 += m*k; | |
| } | |
| this._context.quadraticCurveTo(x1,y1,x,y); | |
| this.x0 = x; this.y0 = y; | |
| break; | |
| } | |
| } | |
| return custom; | |
| } | |
| var line = d3.line() | |
| .x(function(d) { | |
| return d.lon; | |
| }) | |
| .y(function(d) { | |
| return d.lat; | |
| }) | |
| .curve(curve); | |
| var route = svg.selectAll(null) | |
| .data(data1) | |
| .enter() | |
| .append("path") | |
| .attr("class", function(d){ | |
| return d.class | |
| }) | |
| .datum(function(d) { | |
| return [d.source,d.destination]; // d3.line expects an array where each item represnts a vertex. | |
| }) | |
| .attr("d",line) | |
| .style("stroke","yellow") | |
| .style("fill","none") | |
| .style("stroke-width",1.5) | |
| // var testData = [{ | |
| // | |
| // "source": "Honduras", | |
| // "January": "10", | |
| // "February": "5", | |
| // "March": "10", | |
| // "April": "0", | |
| // "May": "10", | |
| // "June": "10", | |
| // "July": "10", | |
| // "August": "10", | |
| // "September": "10", | |
| // "October": "10", | |
| // "November": "10", | |
| // "destination": "Texas" | |
| // }, | |
| // { | |
| // | |
| // "source": "Venezuela", | |
| // "January": "5", | |
| // "February": "10", | |
| // "March": "5", | |
| // "April": "10", | |
| // "May": "10", | |
| // "June": "10", | |
| // "July": "10", | |
| // "August": "10", | |
| // "September": "10", | |
| // "October": "10", | |
| // "November": "10", | |
| // "destination": "Texas" | |
| // }] | |
| // **** this chunck is wrangling the csv into distict datapoints | |
| var months =["January","February","March","April","May", "June","July","August","September","October","November"] | |
| function sumKey(bills,key){ | |
| //https://stackoverflow.com/questions/44436041/how-to-sum-value-of-two-json-object-key | |
| var res = bills.map(bill => Number(bill[key])).reduce((acc, bill) => bill + acc); | |
| return res | |
| } | |
| function getMax(arr, prop) { | |
| var max; | |
| for (var i=0 ; i<arr.length ; i++) { | |
| if (max == null || parseInt(arr[i][prop]) > parseInt(max[prop])) | |
| max = arr[i]; | |
| } | |
| return max; | |
| } | |
| var newTestArray = []; | |
| console.log(mppData); | |
| maxMonthArray = [] | |
| for (var b = 0; b < months.length; b++) { | |
| var maxMonth = getMax(mppData, months[b]); | |
| maxMonthArray.push(maxMonth[months[b]]) | |
| for (var i = 0; i < mppData.length; i++) { | |
| for (var a = 0; a < Number(mppData[i][months[b]]); a++) { | |
| newTestArray.push({ | |
| "id": a, | |
| "source": mppData[i].source, | |
| "destination": mppData[i].destination, | |
| 'month': b | |
| }) | |
| } | |
| } | |
| } | |
| //https://blog.abelotech.com/posts/array-conversion-2-dimensional-javascript/ | |
| function arrayTo2DArray2(list, howMany) { | |
| var idx = 0 | |
| result = [] | |
| while (idx < list.length) { | |
| if (idx % howMany === 0) result.push([]) | |
| result[result.length - 1].push(list[idx++]) | |
| } | |
| return result | |
| } | |
| console.log('newTestArray'); | |
| console.log(newTestArray); | |
| newTestArray.sort(function(a, b) { | |
| return a.id - b.id; | |
| }); | |
| newTestArray.sort(function(a, b) { | |
| return a.month - b.month; | |
| }); | |
| var chunckArray = [] | |
| for (var i = 0; i < maxMonthArray.length; i++) { | |
| maxMonthArray[i] | |
| for (var c = 0; c < Number(maxMonthArray[i]); c++) { | |
| var chunck = newTestArray.filter(obj => obj.id === c && obj.month === i ); | |
| chunckArray.push(chunck) | |
| } | |
| } | |
| console.log('chunckArray'); | |
| console.log(chunckArray); | |
| var combineChunckArray = []; | |
| for (var i = 0; i < chunckArray.length -4; i+= 4) { | |
| combineChunckArray.push(chunckArray[i].concat(chunckArray[i+1],chunckArray[i+2],chunckArray[i+3])) | |
| } | |
| console.log('combineChunckArray.length'); | |
| console.log(combineChunckArray); | |
| var combineChunckArrayLengths =[0] | |
| var addLengths=0; | |
| for (var i = 0; i < combineChunckArray.length; i++) { | |
| for (var b = 0; b < combineChunckArray[i].length; b++) { | |
| // console.log(i); | |
| combineChunckArray[i][b].index = i | |
| // combineChunckArray[i][b].index = i | |
| } | |
| } | |
| for (var i = 0; i < combineChunckArray.length; i++) { | |
| addLengths += combineChunckArray[i].length | |
| combineChunckArrayLengths.push(addLengths) | |
| } | |
| console.log(combineChunckArrayLengths); | |
| var arrayOfNewArrays = arrayTo2DArray2(newTestArray, 20) | |
| // console.log('arrayOfNewArraysff'); | |
| // console.log(arrayOfNewArrays); | |
| // for (var i = 0; i < arrayOfNewArrays.length; i++) { | |
| // | |
| // | |
| // for (var d = 0; d < arrayOfNewArrays[i].length; d++) { | |
| // | |
| // arrayOfNewArrays[i][d].index = i | |
| // | |
| // } | |
| // | |
| // } | |
| // console.log('arrayOfNewArraysff'); | |
| // console.log(arrayOfNewArrays); | |
| function circlesLoop(newTestArrayIndex){ | |
| var multiCircles = svg.selectAll(".circle") | |
| .data(newTestArrayIndex) | |
| .enter().append("circle") | |
| .attr("class", function(d,i){ | |
| // console.log(d); | |
| return `c-${d.month}-${d.id}`}) | |
| .attr("id", function(d,i){ | |
| return `_${d.index}`}) | |
| .attr("r", function(d){ | |
| return 6; | |
| }) | |
| .attr("fill", function(d){ | |
| return 'red'; | |
| }) | |
| } | |
| function multipleTest(aT){ | |
| console.log(`${aT} > ${combineChunckArrayLengths[circleIndexCounter+1]}`); | |
| if (aT >= combineChunckArrayLengths[circleIndexCounter+1] || aT == 0 ){ | |
| circleIndexCounter++ | |
| console.log("monthIndexCounter "+ circleIndexCounter); | |
| console.log('combineChunckArray[circleIndexCounter].length'); | |
| console.log(combineChunckArray[circleIndexCounter].length); | |
| console.log(combineChunckArray[circleIndexCounter]); | |
| circlesLoop(combineChunckArray[circleIndexCounter]) | |
| } | |
| } | |
| //https://stackoverflow.com/questions/1230233/how-to-find-the-sum-of-an-array-of-numbers | |
| var sumMaxMonths = maxMonthArray.reduce(function(acc, val) { return Number(acc) + Number(val) }, 0); | |
| console.log('sumMaxMonths') | |
| console.log(maxMonthArray) | |
| // divide months in half | |
| var arraySplit = [[],[],[],[],[],[],[],[],[],[],[],[]] | |
| for (var i = 0; i < newTestArray.length; i++) { | |
| arraySplit[newTestArray[i].month].push(newTestArray[i]) | |
| } | |
| for (var i = 0; i < arraySplit.length; i++) { | |
| // console.log(arraySplit[i].length); | |
| } | |
| //count twords the sum of the max values in each month | |
| var counter = -1; | |
| var circleIndexCounter = -1; | |
| //migrants in a month | |
| var a = -1; | |
| var remainder; | |
| //total migrants | |
| var aT = 0; | |
| // months | |
| var m = 0; | |
| //This loop transitions the circles | |
| // over the path elements | |
| (function loop() { | |
| //newTestArray.length | |
| if (counter++ > sumMaxMonths ) return; | |
| setTimeout(function() { | |
| if (m < 11) { | |
| if (a++ >= maxMonthArray[m]) { | |
| m++ | |
| a = 0 | |
| } | |
| } | |
| console.log(`.c-${m}-${a}`); | |
| // if (remainder == 0) { | |
| // console.log(remainder); | |
| // } | |
| console.log(aT); | |
| multipleTest(aT) | |
| var thisPolygon = d3.selectAll(`.c-${m}-${a}`) | |
| aT += thisPolygon.nodes().length | |
| // console.log(`.c-${m}-${a}`); | |
| transition1( thisPolygon, counter); | |
| loop() | |
| }, 300) | |
| }()); | |
| function transition1(elem,i) { | |
| elem.transition() | |
| .duration(3000) | |
| .attrTween("transform", translateAlong()) | |
| .on('end', function(d){ | |
| if (d.index > 0) { | |
| d3.selectAll(`#_${d.index-1}`).remove(); | |
| } | |
| }) | |
| // Returns an attrTween for translating along the specified path element. | |
| function translateAlong(path1) { | |
| return function(d, i, a) { | |
| // console.log(`.${d.source}_${d.destination}`); | |
| var path = d3.select(`.${d.source}_${d.destination}`).node(); | |
| var l = path.getTotalLength(); | |
| var t0 = 0; | |
| return function(t) { | |
| var p0 = path.getPointAtLength(t0 * l); //previous point | |
| var p = path.getPointAtLength(t * l); //current point | |
| var angle = Math.atan2(p.y - p0.y, p.x - p0.x) * 180 / Math.PI; //angle for tangent | |
| t0 = t; | |
| //Shifting center to center of arrow | |
| // xoffset and yoffset should be half the original width and height | |
| var xoffset = 0, | |
| yoffset = 0; | |
| var centerX = p.x - xoffset; | |
| var centerY = p.y - yoffset; | |
| return "translate(" + centerX + "," + centerY + ")rotate(" + angle + " " + xoffset + " " + yoffset + ")"; | |
| }; | |
| }; | |
| } | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment