A playful demonstration of using svg's <use>
tag to make a mandala.
Last active
November 10, 2023 09:46
-
-
Save pbeshai/2395deb8b40dcdfdb6e72ee51a6df251 to your computer and use it in GitHub Desktop.
Mandala Generator with D3 and SVG use
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
license: mit | |
height: 720 | |
border: no |
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
function generateMandala(){function t(t,a,e){void 0===a&&(a="y1"),void 0===e&&(e="y2");var r=l(t[a]),n=l(t[e]),i=(sliceHeight-r)*Math.tan(-sliceAngle/2),s=(sliceHeight-n)*Math.tan(sliceAngle/2);return{x1:i,x2:s,y1:r,y2:n}}function a(t){var a=t.x1,e=t.x2,r=t.y1,n=t.y2;return"M"+a+","+r+" L"+e+","+n}var e,r=0,n=d3.range(numMarks).map(function(t,a){var n,i;do i=!0,n=markTypes[Math.floor(Math.random()*markTypes.length)],a>5&&"arrow"===n&&(i=!1);while(!i);e=n;var l;if("point"===n){var s=Math.ceil(20*Math.random())+10;l={type:n,r:s/5,size:s,cumulativeSize:r,y:r+s/2,filled:Math.random()>.3}}else if("arc"===n){var d=Math.ceil(20*Math.random())+2;l={type:n,thickness:Math.round(d/4),size:d,cumulativeSize:r,y:r+d/2}}else if("diagonalUp"===n){var o=Math.ceil(10*Math.random())+3;l={type:n,size:o,cumulativeSize:r,y1:r,y2:r+o}}else if("diagonalDown"===n){var p=Math.ceil(10*Math.random())+3;l={type:n,size:p,cumulativeSize:r,y1:r+p,y2:r}}else if("x"===n){var c=Math.ceil(10*Math.random())+3;l={type:n,size:c,cumulativeSize:r,y1:r+c,y2:r}}else if("arrow"===n){var h=Math.ceil(10*Math.random())+3;l={type:n,size:h,cumulativeSize:r,y1:r+h,yMid:r+h/2,y2:r}}else l={size:0};return l.id=a,r+=l.size,l}),i=d3.nest().key(function(t){return t.type}).object(n),l=d3.scaleLinear().domain([0,r]).range([sliceHeight,0]),s=d3.scaleLinear().domain([0,r]).range([0,sliceHeight]),d=d3.select("#vis-container");d.selectAll("*").remove();var o=d.append("svg").attr("width",width).attr("height",height),p=Math.floor(360*Math.random()),c=.3*Math.random()+.7,h=.15*Math.random()+.05,g=d3.hsl(p,c,h);d3.select("body").style("background"),o.append("rect").attr("class","mandala-bg").attr("width",width).attr("height",height).style("fill",g);var u=o.append("g").attr("transform","translate("+padding.left+" "+padding.top+")");animate&&u.transition().duration(2500).attrTween("transform",function(){return d3.interpolateString("translate("+padding.left+" "+padding.top+") rotate(0 "+plotAreaWidth/2+" "+plotAreaHeight/2+")","translate("+padding.left+" "+padding.top+") rotate(360 "+plotAreaWidth/2+" "+plotAreaHeight/2+")")});var f=u.append("defs"),y=f.append("radialGradient").attr("id","bg-shading").attr("gradientUnits","userSpaceOnUse");y.append("stop").attr("offset","0%").attr("stop-color","#000").attr("stop-opacity",0),y.append("stop").attr("offset","100%").attr("stop-color","#000").attr("stop-opacity",.2),o.insert("rect","g").attr("class","mandala-bg-shading").attr("width",width).attr("height",height).style("fill","url(#bg-shading)");var m=f.append("clipPath").attr("id","marks-clip").append("circle").attr("cx",plotAreaWidth/2).attr("cy",plotAreaHeight/2).attr("r",0).style("fill","#fff");animate?m.transition().ease(d3.easeLinear).duration(2e3).attr("r",plotAreaHeight/2+5):m.attr("r",plotAreaHeight/2+5);var v=u.append("g").attr("class","slices-group").attr("clip-path","url(#marks-clip)"),A=v.append("g").attr("id","ref-slice").attr("class","slice").attr("transform","translate("+plotAreaWidth/2+" 0)").attr("clip-path","url(#slice-clip)"),M=d3.range(numSlices-1).map(function(t,a){return{id:a+1,href:"#ref-slice",transform:"rotate("+(a+1)*sliceAngle*(180/Math.PI)+" "+plotAreaWidth/2+" "+sliceHeight+")"}}),k=v.selectAll("copy-slice").data(M);k.enter().append("use").attr("xlink:href",function(t){return t.href}).attr("transform",function(t){return t.transform}),A.append("path").attr("class","slice-bg").attr("transform","translate(0 "+sliceHeight+")").attr("d",arc({innerRadius:0,outerRadius:sliceHeight,startAngle:-(sliceAngle/2),endAngle:sliceAngle/2})).style("fill","none").style("stroke","tomato").style("opacity",0);var w="#fff",H=A.selectAll(".point").data(i.point||[]);H.enter().append("circle").attr("class","point").attr("r",function(t){return t.r}).attr("cx",0).attr("cy",function(t){return l(t.y)}).style("fill",function(t){return t.filled?w:"none"}).style("stroke",function(t){return t.filled?"none":w});var z=A.selectAll(".arc").data(i.arc||[]),x=d3.arc().innerRadius(function(t){return s(t.y-t.thickness)}).outerRadius(function(t){return s(t.y)}).startAngle(-sliceAngle/2-.1).endAngle(sliceAngle/2+.1);z.enter().append("path").attr("transform","translate(0 "+sliceHeight+")").attr("class","arc").attr("d",x).style("fill",w);var S=A.selectAll(".diagonalUp").data(i.diagonalUp||[]);S.enter().append("path").attr("class","diagonalUp").attr("d",function(e){return a(t(e))}).style("stroke",w).style("fill",w);var b=A.selectAll(".diagonalDown").data(i.diagonalDown||[]);b.enter().append("path").attr("class","diagonalDown").attr("d",function(e){return a(t(e))}).style("stroke",w).style("fill",w);var U=A.selectAll(".x").data(i.x||[]),W=U.enter().append("g").attr("class","x");W.append("path").attr("d",function(e){return a(t(e))}).style("stroke",w).style("fill",w),W.append("path").attr("d",function(e){return a(t(e,"y2","y1"))}).style("stroke",w).style("fill",w);var D=A.selectAll(".arrow").data(i.arrow||[]),L=D.enter().append("g").attr("class","arrow");L.append("path").attr("d",function(e){return a(t(e,"y1","yMid"))}).style("stroke",w).style("fill",w),L.append("path").attr("d",function(e){return a(t(e,"y2","yMid"))}).style("stroke",w).style("fill",w)}var markTypes=["x","arrow","arc","point"],animate=!0,numMarks=30,width=600,height=600,padding={top:20,right:20,bottom:20,left:20},plotAreaWidth=width-padding.left-padding.right,plotAreaHeight=height-padding.top-padding.bottom,numSlices=32,sliceHeight=plotAreaHeight/2,sliceAngle=2*Math.PI/numSlices,arc=d3.arc();generateMandala(),d3.select("#make-mandala").on("click",generateMandala); | |
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJnZW5lcmF0ZU1hbmRhbGEiLCJkVG9MaW5lIiwiZCIsInkxS2V5IiwieTJLZXkiLCJjb25zdCIsInkxIiwieVNjYWxlIiwieTIiLCJ4MSIsInNsaWNlSGVpZ2h0IiwiTWF0aCIsInRhbiIsInNsaWNlQW5nbGUiLCJ4MiIsInRvUGF0aCIsInJlZiIsImxldCIsInByZXZUeXBlIiwiY3VtdWxhdGl2ZVNpemUiLCJkYXRhIiwiZDMiLCJyYW5nZSIsIm51bU1hcmtzIiwibWFwIiwiaSIsInR5cGUiLCJ2YWxpZFR5cGUiLCJtYXJrVHlwZXMiLCJmbG9vciIsInJhbmRvbSIsImxlbmd0aCIsIml0ZW0iLCJzaXplIiwiY2VpbCIsInIiLCJ5IiwiZmlsbGVkIiwidGhpY2tuZXNzIiwicm91bmQiLCJ5TWlkIiwiaWQiLCJkYXRhQnlUeXBlIiwibmVzdCIsImtleSIsIm9iamVjdCIsInNjYWxlTGluZWFyIiwiZG9tYWluIiwiclNjYWxlIiwiY29udGFpbmVyIiwic2VsZWN0Iiwic2VsZWN0QWxsIiwicmVtb3ZlIiwic3ZnIiwiYXBwZW5kIiwiYXR0ciIsIndpZHRoIiwiaGVpZ2h0IiwiYmdIdWUiLCJiZ1NhdHVyYXRpb24iLCJiZ0xpZ2h0bmVzcyIsImJnIiwiaHNsIiwic3R5bGUiLCJnIiwicGFkZGluZyIsImFuaW1hdGUiLCJ0cmFuc2l0aW9uIiwiZHVyYXRpb24iLCJhdHRyVHdlZW4iLCJpbnRlcnBvbGF0ZVN0cmluZyIsInBsb3RBcmVhV2lkdGgiLCJwbG90QXJlYUhlaWdodCIsImRlZnMiLCJtYW5kYWxhQmdHcmFkIiwiaW5zZXJ0IiwibWFya3NDbGlwIiwiZWFzZSIsImVhc2VMaW5lYXIiLCJnU2xpY2VzIiwic2xpY2UiLCJjb3B5U2xpY2VzIiwibnVtU2xpY2VzIiwiaHJlZiIsInRyYW5zZm9ybSIsIlBJIiwic2xpY2VCaW5kaW5nIiwiZW50ZXIiLCJhcmMiLCJpbm5lclJhZGl1cyIsIm91dGVyUmFkaXVzIiwic3RhcnRBbmdsZSIsImVuZEFuZ2xlIiwibWFya0NvbG9yIiwicG9pbnRzIiwicG9pbnQiLCJhcmNzIiwiaW50ZXJpb3JBcmMiLCJkaWFnVXAiLCJkaWFnb25hbFVwIiwiZGlhZ0Rvd24iLCJkaWFnb25hbERvd24iLCJ4TWFya3MiLCJ4IiwieE1hcmtHcyIsImFycm93TWFya3MiLCJhcnJvdyIsImFycm93TWFya0dzIiwidG9wIiwicmlnaHQiLCJib3R0b20iLCJsZWZ0Iiwib24iXSwibWFwcGluZ3MiOiJBQThCQSxRQUFTQSxtQkFvUVIsUUFBU0MsR0FBUUMsRUFBR0MsRUFBUUMsa0JBQUEscUJBQVcsS0FDckNDLElBQU1DLEdBQUtDLEVBQU9MLEVBQUVDLElBRDJCSyxFQUFBRCxFQUFBTCxFQUFBRSxJQUN2Q0ssR0FBR0MsWUFBaUJKLEdBQUFLLEtBQUFDLEtBQUFDLFdBQUEsR0FDcEJDLEdBQUdKLFlBQWlCRixHQUFBRyxLQUFBQyxJQUFBQyxXQUFBLEVBRTVCUixRQUdFSSxHQUFBQSxFQURGSyxHQUFBQSxFQUNFUixHQUFBQSxFQUNBRSxHQUFBQSxHQU1KLFFBRkNPLEdBQUFDLE1BQUFQLEdBQUFPLEVBQUFQLEdBQUFLLEVBQUFFLEVBQUFGLEdBQUFSLEVBQUFVLEVBQUFWLEdBQUFFLEVBQUFRLEVBQUFSLEVBR0MsT0FBTyxJQUFJQyxFQUFFLElBQUlILEVBQUUsS0FBS1EsRUFBRSxJQUFJTixFQWpSL0JTLEdBQ0lDLEdBREFDLEVBQWlCLEVBRWZDLEVBQVNDLEdBQUNDLE1BQU1DLFVBQVVDLElBQUksU0FBQXRCLEVBQUF1QixHQUNsQ1IsR0FBSVMsR0FDQUMsQ0FDSixHQUNFQSxJQUFZLEVBQ1pELEVBQU9FLFVBQVVqQixLQUFLa0IsTUFBTWxCLEtBQUttQixTQUFXRixVQUFVRyxTQUVsRE4sRUFBSSxHQUFjLFVBQVRDLElBQ1hDLEdBQVksVUFFTkEsRUFHVlQsR0FBV1EsQ0FFWFQsSUFBSWUsRUFFSixJQUFhLFVBQVROLEVBQWtCLENBQ3BCckIsR0FBTTRCLEdBQU90QixLQUFLdUIsS0FBdUIsR0FBbEJ2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QixFQVVieEYsSUFBTW9HLEdBQWF6QixFQUFNN0IsVUFBVSxVQUFVL0IsS0FBS3NCLEVBQVdnRSxXQUwxREMsRUFBVUYsRUFBRWxCLFFBQ1pqQyxPQUFNLEtBQ05DLEtBQUssUUFBUyxRQVNqQm9ELEdBQVlyRCxPQUFPLFFBTmRDLEtBQUMsSUFBQSxTQUFBckQsR0FBVSxNQUFHYSxHQUFNZCxFQUFVQyxFQUFBLEtBQVMsV0FRekM2RCxNQUFNLFNBQVU4QixHQU5kOUIsTUFBQyxPQUFXOEIsR0FTakJjLEVBUFFyRCxPQUFTLFFBUWRDLEtBQUssSUFBSyxTQUFBckQsR0FBRSxNQUFHYSxHQUFPZCxFQUFRQyxFQUFHLEtBQU0sV0FOMUM2RCxNQUFBLFNBQW1COEIsR0FDaEI5QixNQUFLLE9BQUs4QixHQTdWZHhGLEdBQU11QixZQUFhLElBQUssUUFBUyxNQUFPLFNBQ2xDc0MsU0FBVSxFQUNWM0MsU0FBYSxHQUliaUMsTUFBUSxJQUNSQyxPQUFTLElBR1RRLFNBQ0oyQyxJQUFLLEdBQ0xDLE1BQU8sR0FDUEMsT0FBUSxHQUNSQyxLQUFNLElBSUZ4QyxjQUFnQmYsTUFBUVMsUUFBUThDLEtBQU85QyxRQUFRNEMsTUFDL0NyQyxlQUFpQmYsT0FBU1EsUUFBUTJDLElBQU0zQyxRQUFRNkMsT0FHaEQ1QixVQUFjLEdBQ2R4RSxZQUFjOEQsZUFBbUIsRUFDakMzRCxXQUFlLEVBQUdGLEtBQU8wRSxHQUFJSCxVQUU3Qk0sSUFBUW5FLEdBQUNtRSxLQTZVZnhGLG1CQUVBcUIsR0FBRzZCLE9BUE0saUJBQU84RCxHQUFBLFFBQUdoSCIsImZpbGUiOiJzY3JpcHQuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBtYXJrIHR5cGVzXG4vLyBjb25zdCBtYXJrVHlwZXMgPSBbJ2RpYWdvbmFsVXAnLCAnZGlhZ29uYWxEb3duJywgJ3gnLCAnYXJjJywgJ3BvaW50J107IC8vICdzcXVhcmUnXTtcbmNvbnN0IG1hcmtUeXBlcyA9IFsneCcsICdhcnJvdycsICdhcmMnLCAncG9pbnQnXTsgLy8gJ3NxdWFyZSddO1xuY29uc3QgYW5pbWF0ZSA9IHRydWU7XG5jb25zdCBudW1NYXJrcyA9IDMwO1xuXG5cbi8vIG91dGVyIHN2ZyBkaW1lbnNpb25zXG5jb25zdCB3aWR0aCA9IDYwMDtcbmNvbnN0IGhlaWdodCA9IDYwMDtcblxuLy8gcGFkZGluZyBhcm91bmQgdGhlIGNoYXJ0XG5jb25zdCBwYWRkaW5nID0ge1xuICB0b3A6IDIwLFxuICByaWdodDogMjAsXG4gIGJvdHRvbTogMjAsXG4gIGxlZnQ6IDIwLFxufTtcblxuLy8gaW5uZXIgY2hhcnQgZGltZW5zaW9ucywgd2hlcmUgdGhlIGRvdHMgYXJlIHBsb3R0ZWRcbmNvbnN0IHBsb3RBcmVhV2lkdGggPSB3aWR0aCAtIHBhZGRpbmcubGVmdCAtIHBhZGRpbmcucmlnaHQ7XG5jb25zdCBwbG90QXJlYUhlaWdodCA9IGhlaWdodCAtIHBhZGRpbmcudG9wIC0gcGFkZGluZy5ib3R0b207XG5cbi8vIHNpemUgb2YgYW4gaW5kaXZpZHVhbCBzbGljZVxuY29uc3QgbnVtU2xpY2VzID0gMzI7XG5jb25zdCBzbGljZUhlaWdodCA9IHBsb3RBcmVhSGVpZ2h0IC8gMjtcbmNvbnN0IHNsaWNlQW5nbGUgPSAoMiAqIE1hdGguUEkpIC8gbnVtU2xpY2VzO1xuXG5jb25zdCBhcmMgPSBkMy5hcmMoKTtcblxuZnVuY3Rpb24gZ2VuZXJhdGVNYW5kYWxhKCkge1xuICAvLyBnZW5lcmF0ZSByYW5kb20gZGF0YVxuICBsZXQgY3VtdWxhdGl2ZVNpemUgPSAwO1xuICBsZXQgcHJldlR5cGU7XG4gIGNvbnN0IGRhdGEgPSBkMy5yYW5nZShudW1NYXJrcykubWFwKChkLCBpKSA9PiB7XG4gICAgbGV0IHR5cGU7XG4gICAgbGV0IHZhbGlkVHlwZTtcbiAgICBkbyB7XG4gICAgICB2YWxpZFR5cGUgPSB0cnVlO1xuICAgICAgdHlwZSA9IG1hcmtUeXBlc1tNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiBtYXJrVHlwZXMubGVuZ3RoKV07XG5cbiAgICAgIGlmIChpID4gNSAmJiB0eXBlID09PSAnYXJyb3cnKSB7XG4gICAgICAgIHZhbGlkVHlwZSA9IGZhbHNlO1xuICAgICAgfVxuICAgIH0gd2hpbGUgKCF2YWxpZFR5cGUpO1xuXG4gICAgLy8gdHlwZSA9ICdhcnJvdyc7XG4gICAgcHJldlR5cGUgPSB0eXBlO1xuXG4gICAgbGV0IGl0ZW07XG5cbiAgICBpZiAodHlwZSA9PT0gJ3BvaW50Jykge1xuICAgICAgY29uc3Qgc2l6ZSA9IE1hdGguY2VpbChNYXRoLnJhbmRvbSgpICogMjApICsgMTA7XG4gICAgICBpdGVtID0ge1xuICAgICAgICB0eXBlLFxuICAgICAgICByOiBzaXplIC8gNSxcbiAgICAgICAgc2l6ZSxcbiAgICAgICAgY3VtdWxhdGl2ZVNpemUsXG4gICAgICAgIHk6IGN1bXVsYXRpdmVTaXplICsgKHNpemUgLyAyKSxcbiAgICAgICAgZmlsbGVkOiBNYXRoLnJhbmRvbSgpID4gMC4zLFxuICAgICAgfTtcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdhcmMnKSB7XG4gICAgICBjb25zdCBzaXplID0gTWF0aC5jZWlsKE1hdGgucmFuZG9tKCkgKiAyMCkgKyAyO1xuICAgICAgaXRlbSA9IHtcbiAgICAgICAgdHlwZSxcbiAgICAgICAgdGhpY2tuZXNzOiBNYXRoLnJvdW5kKHNpemUgLyA0KSxcbiAgICAgICAgc2l6ZSxcbiAgICAgICAgY3VtdWxhdGl2ZVNpemUsXG4gICAgICAgIHk6IGN1bXVsYXRpdmVTaXplICsgKHNpemUgLyAyKSxcbiAgICAgIH07XG4gICAgfSBlbHNlIGlmICh0eXBlID09PSAnZGlhZ29uYWxVcCcpIHtcbiAgICAgIGNvbnN0IHNpemUgPSBNYXRoLmNlaWwoTWF0aC5yYW5kb20oKSAqIDEwKSArIDM7XG4gICAgICBpdGVtID0ge1xuICAgICAgICB0eXBlLFxuICAgICAgICBzaXplLFxuICAgICAgICBjdW11bGF0aXZlU2l6ZSxcbiAgICAgICAgeTE6IGN1bXVsYXRpdmVTaXplLFxuICAgICAgICB5MjogY3VtdWxhdGl2ZVNpemUgKyBzaXplLFxuICAgICAgfTtcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdkaWFnb25hbERvd24nKSB7XG4gICAgICBjb25zdCBzaXplID0gTWF0aC5jZWlsKE1hdGgucmFuZG9tKCkgKiAxMCkgKyAzO1xuICAgICAgaXRlbSA9IHtcbiAgICAgICAgdHlwZSxcbiAgICAgICAgc2l6ZSxcbiAgICAgICAgY3VtdWxhdGl2ZVNpemUsXG4gICAgICAgIHkxOiBjdW11bGF0aXZlU2l6ZSArIHNpemUsXG4gICAgICAgIHkyOiBjdW11bGF0aXZlU2l6ZSxcbiAgICAgIH07XG4gICAgfSBlbHNlIGlmICh0eXBlID09PSAneCcpIHtcbiAgICAgIGNvbnN0IHNpemUgPSBNYXRoLmNlaWwoTWF0aC5yYW5kb20oKSAqIDEwKSArIDM7XG4gICAgICBpdGVtID0ge1xuICAgICAgICB0eXBlLFxuICAgICAgICBzaXplLFxuICAgICAgICBjdW11bGF0aXZlU2l6ZSxcbiAgICAgICAgeTE6IGN1bXVsYXRpdmVTaXplICsgc2l6ZSxcbiAgICAgICAgeTI6IGN1bXVsYXRpdmVTaXplLFxuICAgICAgfTtcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdhcnJvdycpIHtcbiAgICAgIGNvbnN0IHNpemUgPSBNYXRoLmNlaWwoTWF0aC5yYW5kb20oKSAqIDEwKSArIDM7XG4gICAgICBpdGVtID0ge1xuICAgICAgICB0eXBlLFxuICAgICAgICBzaXplLFxuICAgICAgICBjdW11bGF0aXZlU2l6ZSxcbiAgICAgICAgeTE6IGN1bXVsYXRpdmVTaXplICsgc2l6ZSxcbiAgICAgICAgeU1pZDogY3VtdWxhdGl2ZVNpemUgKyAoc2l6ZSAvIDIpLFxuICAgICAgICB5MjogY3VtdWxhdGl2ZVNpemUsXG4gICAgICB9O1xuICAgIH0gZWxzZSB7XG4gICAgICBpdGVtID0geyBzaXplOiAwIH07XG4gICAgfVxuXG4gICAgaXRlbS5pZCA9IGk7XG4gICAgY3VtdWxhdGl2ZVNpemUgKz0gaXRlbS5zaXplO1xuXG4gICAgcmV0dXJuIGl0ZW07XG4gIH0pO1xuXG4gIGNvbnN0IGRhdGFCeVR5cGUgPSBkMy5uZXN0KCkua2V5KGQgPT4gZC50eXBlKS5vYmplY3QoZGF0YSk7XG5cbiAgLy8gaW5pdGlhbGl6ZSBzY2FsZXNcbiAgY29uc3QgeVNjYWxlID0gZDMuc2NhbGVMaW5lYXIoKS5kb21haW4oWzAsIGN1bXVsYXRpdmVTaXplXSkucmFuZ2UoW3NsaWNlSGVpZ2h0LCAwXSk7XG4gIGNvbnN0IHJTY2FsZSA9IGQzLnNjYWxlTGluZWFyKCkuZG9tYWluKFswLCBjdW11bGF0aXZlU2l6ZV0pLnJhbmdlKFswLCBzbGljZUhlaWdodF0pO1xuXG5cdC8vIHNlbGVjdCB0aGUgcm9vdCBjb250YWluZXIgd2hlcmUgdGhlIGNoYXJ0IHdpbGwgYmUgYWRkZWRcblx0Y29uc3QgY29udGFpbmVyID0gZDMuc2VsZWN0KCcjdmlzLWNvbnRhaW5lcicpO1xuXG5cdC8vIGNsZWFyIGFueSBvbGQgY29udGVudHNcblx0Y29udGFpbmVyLnNlbGVjdEFsbCgnKicpLnJlbW92ZSgpO1xuXG5cdC8vIGluaXRpYWxpemUgbWFpbiBTVkdcblx0Y29uc3Qgc3ZnID0gY29udGFpbmVyLmFwcGVuZCgnc3ZnJylcblx0ICAuYXR0cignd2lkdGgnLCB3aWR0aClcblx0ICAuYXR0cignaGVpZ2h0JywgaGVpZ2h0KTtcblxuXG5cdGNvbnN0IGJnSHVlID0gTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogMzYwKTtcblx0Y29uc3QgYmdTYXR1cmF0aW9uID0gKE1hdGgucmFuZG9tKCkgKiAwLjMpICsgMC43O1xuXHRjb25zdCBiZ0xpZ2h0bmVzcyA9IChNYXRoLnJhbmRvbSgpICogMC4xNSkgKyAwLjA1O1xuXG5cdGNvbnN0IGJnID0gZDMuaHNsKGJnSHVlLCBiZ1NhdHVyYXRpb24sIGJnTGlnaHRuZXNzKTtcblx0ZDMuc2VsZWN0KCdib2R5Jykuc3R5bGUoJ2JhY2tncm91bmQnKTtcblxuXHQvLyBkcmF3IHRoZSBiYWNrZ3JvdW5kXG5cdHN2Zy5hcHBlbmQoJ3JlY3QnKVxuXHQgIC5hdHRyKCdjbGFzcycsICdtYW5kYWxhLWJnJylcblx0ICAuYXR0cignd2lkdGgnLCB3aWR0aClcblx0ICAuYXR0cignaGVpZ2h0JywgaGVpZ2h0KVxuXHQgIC5zdHlsZSgnZmlsbCcsIGJnKTtcblxuXHQvLyB0aGUgbWFpbiA8Zz4gd2hlcmUgYWxsIHRoZSBjaGFydCBjb250ZW50IGdvZXMgaW5zaWRlXG5cdGNvbnN0IGcgPSBzdmcuYXBwZW5kKCdnJylcblx0ICAuYXR0cigndHJhbnNmb3JtJywgYHRyYW5zbGF0ZSgke3BhZGRpbmcubGVmdH0gJHtwYWRkaW5nLnRvcH0pYCk7XG5cblx0aWYgKGFuaW1hdGUpIHtcblx0ICBnLnRyYW5zaXRpb24oKVxuXHQgICAgLmR1cmF0aW9uKDI1MDApXG5cdCAgICAuYXR0clR3ZWVuKCd0cmFuc2Zvcm0nLCAoKSA9PlxuXHQgICAgICBkMy5pbnRlcnBvbGF0ZVN0cmluZyhgdHJhbnNsYXRlKCR7cGFkZGluZy5sZWZ0fSAke3BhZGRpbmcudG9wfSkgcm90YXRlKDAgJHtwbG90QXJlYVdpZHRoIC8gMn0gJHtwbG90QXJlYUhlaWdodCAvIDJ9KWAsXG5cdCAgICAgICAgYHRyYW5zbGF0ZSgke3BhZGRpbmcubGVmdH0gJHtwYWRkaW5nLnRvcH0pIHJvdGF0ZSgzNjAgJHtwbG90QXJlYVdpZHRoIC8gMn0gJHtwbG90QXJlYUhlaWdodCAvIDJ9KWApKTtcblx0fVxuXG5cblx0Y29uc3QgZGVmcyA9IGcuYXBwZW5kKCdkZWZzJyk7XG5cblx0Ly8gY2xpcCBwYXRoIGZvciBzbGljZXMgZGlzYWJsZWQgdG8gYWxsb3cgc29tZSBzbGlnaHQgb3ZlcmxhcCBmb3IgdGhpbmdzIGxpa2UgYXJjc1xuXHQvLyBhZGQgdGhlIHNsaWNlIGFzIGEgY2xpcCBwYXRoXG5cdC8vIGRlZnMuYXBwZW5kKCdjbGlwUGF0aCcpXG5cdC8vICAgLmF0dHIoJ2lkJywgJ3NsaWNlLWNsaXAnKVxuXHQvLyAgIC5hcHBlbmQoJ3BhdGgnKVxuXHQvLyAgIC5hdHRyKCd0cmFuc2Zvcm0nLCBgdHJhbnNsYXRlKDAgJHtzbGljZUhlaWdodH0pYClcblx0Ly8gICAuYXR0cignZCcsIGFyYyh7XG5cdC8vICAgICBpbm5lclJhZGl1czogMCxcblx0Ly8gICAgIG91dGVyUmFkaXVzOiBzbGljZUhlaWdodCxcblx0Ly8gICAgIHN0YXJ0QW5nbGU6IC0oc2xpY2VBbmdsZSAvIDIpLFxuXHQvLyAgICAgZW5kQW5nbGU6IHNsaWNlQW5nbGUgLyAyLFxuXHQvLyAgIH0pKVxuXHQvLyAgIC5zdHlsZSgnZmlsbCcsICd0b21hdG8nKVxuXHQvLyAgIC5zdHlsZSgnc3Ryb2tlJywgJ3RvbWF0bycpXG5cdC8vICAgLnN0eWxlKCdzdHJva2Utd2lkdGgnLCA1KTtcblxuXHQvLyByYWRpYWwgZ3JhZGllbnQgZm9yIGJhY2tncm91bmRcblx0Y29uc3QgbWFuZGFsYUJnR3JhZCA9IGRlZnMuYXBwZW5kKCdyYWRpYWxHcmFkaWVudCcpXG5cdCAgLmF0dHIoJ2lkJywgJ2JnLXNoYWRpbmcnKVxuXHQgIC5hdHRyKCdncmFkaWVudFVuaXRzJywgJ3VzZXJTcGFjZU9uVXNlJyk7XG5cblx0bWFuZGFsYUJnR3JhZC5hcHBlbmQoJ3N0b3AnKVxuXHQgIC5hdHRyKCdvZmZzZXQnLCAnMCUnKVxuXHQgIC5hdHRyKCdzdG9wLWNvbG9yJywgJyMwMDAnKVxuXHQgIC5hdHRyKCdzdG9wLW9wYWNpdHknLCAwLjApO1xuXG5cdG1hbmRhbGFCZ0dyYWQuYXBwZW5kKCdzdG9wJylcblx0ICAuYXR0cignb2Zmc2V0JywgJzEwMCUnKVxuXHQgIC5hdHRyKCdzdG9wLWNvbG9yJywgJyMwMDAnKVxuXHQgIC5hdHRyKCdzdG9wLW9wYWNpdHknLCAwLjIpO1xuXG5cdHN2Zy5pbnNlcnQoJ3JlY3QnLCAnZycpXG5cdCAgLmF0dHIoJ2NsYXNzJywgJ21hbmRhbGEtYmctc2hhZGluZycpXG5cdCAgLmF0dHIoJ3dpZHRoJywgd2lkdGgpXG5cdCAgLmF0dHIoJ2hlaWdodCcsIGhlaWdodClcblx0ICAuc3R5bGUoJ2ZpbGwnLCAndXJsKCNiZy1zaGFkaW5nKScpO1xuXG5cdC8vIGFkZCBpbiBhIGJpZyBjbGlwIGZvciBhbGwgdGhlIG1hcmtzXG5cdGNvbnN0IG1hcmtzQ2xpcCA9IGRlZnMuYXBwZW5kKCdjbGlwUGF0aCcpXG5cdCAgLmF0dHIoJ2lkJywgJ21hcmtzLWNsaXAnKVxuXHQgIC5hcHBlbmQoJ2NpcmNsZScpXG5cdCAgLmF0dHIoJ2N4JywgcGxvdEFyZWFXaWR0aCAvIDIpXG5cdCAgLmF0dHIoJ2N5JywgcGxvdEFyZWFIZWlnaHQgLyAyKVxuXHQgIC5hdHRyKCdyJywgMClcblx0ICAuc3R5bGUoJ2ZpbGwnLCAnI2ZmZicpO1xuXG5cdGlmIChhbmltYXRlKSB7XG5cdCAgbWFya3NDbGlwLnRyYW5zaXRpb24oKVxuXHQgICAgLmVhc2UoZDMuZWFzZUxpbmVhcilcblx0ICAgIC5kdXJhdGlvbigyMDAwKVxuXHQgICAgLmF0dHIoJ3InLCAocGxvdEFyZWFIZWlnaHQgLyAyKSArIDUpO1xuXHR9IGVsc2Uge1xuXHQgIG1hcmtzQ2xpcFxuXHQgICAgLmF0dHIoJ3InLCAocGxvdEFyZWFIZWlnaHQgLyAyKSArIDUpO1xuXHR9XG5cblx0Y29uc3QgZ1NsaWNlcyA9IGcuYXBwZW5kKCdnJylcblx0ICAuYXR0cignY2xhc3MnLCAnc2xpY2VzLWdyb3VwJylcblx0ICAuYXR0cignY2xpcC1wYXRoJywgJ3VybCgjbWFya3MtY2xpcCknKTtcblxuXHQvLyBjcmVhdGUgdGhlIGdyb3VwIHRvIGJlIHJlcGVhdGVkXG5cdGNvbnN0IHNsaWNlID0gZ1NsaWNlcy5hcHBlbmQoJ2cnKVxuXHQgIC5hdHRyKCdpZCcsICdyZWYtc2xpY2UnKVxuXHQgIC5hdHRyKCdjbGFzcycsICdzbGljZScpXG5cdCAgLmF0dHIoJ3RyYW5zZm9ybScsIGB0cmFuc2xhdGUoJHtwbG90QXJlYVdpZHRoIC8gMn0gMClgKVxuXHQgIC5hdHRyKCdjbGlwLXBhdGgnLCAndXJsKCNzbGljZS1jbGlwKScpO1xuXG5cdC8vIGFkZCBpbiBjb3BpZXMgb2YgdGhpcyBzbGljZVxuXHRjb25zdCBjb3B5U2xpY2VzID0gZDMucmFuZ2UobnVtU2xpY2VzIC0gMSkubWFwKChkLCBpKSA9PiAoe1xuXHQgIGlkOiBpICsgMSxcblx0ICBocmVmOiAnI3JlZi1zbGljZScsXG5cdCAgdHJhbnNmb3JtOiBgcm90YXRlKCR7KGkgKyAxKSAqIHNsaWNlQW5nbGUgKiAoMTgwIC8gTWF0aC5QSSl9ICR7cGxvdEFyZWFXaWR0aCAvIDJ9ICR7c2xpY2VIZWlnaHR9KWAsXG5cdH0pKTtcblxuXHRjb25zdCBzbGljZUJpbmRpbmcgPSBnU2xpY2VzLnNlbGVjdEFsbCgnY29weS1zbGljZScpLmRhdGEoY29weVNsaWNlcyk7XG5cdHNsaWNlQmluZGluZy5lbnRlcigpLmFwcGVuZCgndXNlJylcblx0ICAuYXR0cigneGxpbms6aHJlZicsIGQgPT4gZC5ocmVmKVxuXHQgIC5hdHRyKCd0cmFuc2Zvcm0nLCBkID0+IGQudHJhbnNmb3JtKTtcblxuXHQvLyBidWlsZCB1cCB0aGUgc2xpY2Vcblx0c2xpY2UuYXBwZW5kKCdwYXRoJylcblx0ICAuYXR0cignY2xhc3MnLCAnc2xpY2UtYmcnKVxuXHQgIC5hdHRyKCd0cmFuc2Zvcm0nLCBgdHJhbnNsYXRlKDAgJHtzbGljZUhlaWdodH0pYClcblx0ICAuYXR0cignZCcsIGFyYyh7XG5cdCAgICBpbm5lclJhZGl1czogMCxcblx0ICAgIG91dGVyUmFkaXVzOiBzbGljZUhlaWdodCxcblx0ICAgIHN0YXJ0QW5nbGU6IC0oc2xpY2VBbmdsZSAvIDIpLFxuXHQgICAgZW5kQW5nbGU6IHNsaWNlQW5nbGUgLyAyLFxuXHQgIH0pKVxuXHQgIC5zdHlsZSgnZmlsbCcsICdub25lJylcblx0ICAuc3R5bGUoJ3N0cm9rZScsICd0b21hdG8nKVxuXHQgIC5zdHlsZSgnb3BhY2l0eScsIDAuMCk7XG5cblx0Y29uc3QgbWFya0NvbG9yID0gJyNmZmYnO1xuXG5cdC8vIGFkZCBwb2ludHMgdG8gdGhlIHNsaWNlXG5cdGNvbnN0IHBvaW50cyA9IHNsaWNlLnNlbGVjdEFsbCgnLnBvaW50JykuZGF0YShkYXRhQnlUeXBlLnBvaW50IHx8IFtdKTtcblxuXHRwb2ludHMuZW50ZXIoKVxuXHQgIC5hcHBlbmQoJ2NpcmNsZScpXG5cdCAgLmF0dHIoJ2NsYXNzJywgJ3BvaW50Jylcblx0ICAuYXR0cigncicsIGQgPT4gZC5yKVxuXHQgIC5hdHRyKCdjeCcsIDApXG5cdCAgLmF0dHIoJ2N5JywgZCA9PiB5U2NhbGUoZC55KSlcblx0ICAuc3R5bGUoJ2ZpbGwnLCBkID0+IChkLmZpbGxlZCA/IG1hcmtDb2xvciA6ICdub25lJykpXG5cdCAgLnN0eWxlKCdzdHJva2UnLCBkID0+IChkLmZpbGxlZCA/ICdub25lJyA6IG1hcmtDb2xvcikpO1xuXG5cdC8vIGFkZCBhcmNzXG5cdGNvbnN0IGFyY3MgPSBzbGljZS5zZWxlY3RBbGwoJy5hcmMnKS5kYXRhKGRhdGFCeVR5cGUuYXJjIHx8IFtdKTtcblxuXHRjb25zdCBpbnRlcmlvckFyYyA9IGQzLmFyYygpXG5cdCAgLmlubmVyUmFkaXVzKGQgPT4gclNjYWxlKGQueSAtIGQudGhpY2tuZXNzKSlcblx0ICAub3V0ZXJSYWRpdXMoZCA9PiByU2NhbGUoZC55KSlcblx0ICAuc3RhcnRBbmdsZSgoLXNsaWNlQW5nbGUgLyAyKSAtIDAuMSkgLy8gc2xpZ2h0IHBhZGRpbmcgdG8gZW5zdXJlIG92ZXJsYXBcblx0ICAuZW5kQW5nbGUoKHNsaWNlQW5nbGUgLyAyKSArIDAuMSk7XG5cblx0YXJjcy5lbnRlcigpXG5cdCAgLmFwcGVuZCgncGF0aCcpXG5cdCAgLmF0dHIoJ3RyYW5zZm9ybScsIGB0cmFuc2xhdGUoMCAke3NsaWNlSGVpZ2h0fSlgKVxuXHQgIC5hdHRyKCdjbGFzcycsICdhcmMnKVxuXHQgIC5hdHRyKCdkJywgaW50ZXJpb3JBcmMpXG5cdCAgLnN0eWxlKCdmaWxsJywgbWFya0NvbG9yKTtcblxuXHQvLyBhZGQgZGlhZ29uYWwgbGluZSB1cFxuXHRjb25zdCBkaWFnVXAgPSBzbGljZS5zZWxlY3RBbGwoJy5kaWFnb25hbFVwJykuZGF0YShkYXRhQnlUeXBlLmRpYWdvbmFsVXAgfHwgW10pO1xuXG5cdGZ1bmN0aW9uIGRUb0xpbmUoZCwgeTFLZXkgPSAneTEnLCB5MktleSA9ICd5MicpIHtcblx0ICBjb25zdCB5MSA9IHlTY2FsZShkW3kxS2V5XSk7XG5cdCAgY29uc3QgeTIgPSB5U2NhbGUoZFt5MktleV0pO1xuXHQgIGNvbnN0IHgxID0gKHNsaWNlSGVpZ2h0IC0geTEpICogTWF0aC50YW4oLXNsaWNlQW5nbGUgLyAyKTtcblx0ICBjb25zdCB4MiA9IChzbGljZUhlaWdodCAtIHkyKSAqIE1hdGgudGFuKHNsaWNlQW5nbGUgLyAyKTtcblxuXHQgIHJldHVybiB7XG5cdCAgICB4MSxcblx0ICAgIHgyLFxuXHQgICAgeTEsXG5cdCAgICB5Mixcblx0ICB9O1xuXHR9XG5cblx0ZnVuY3Rpb24gdG9QYXRoKHsgeDEsIHgyLCB5MSwgeTIgfSkge1xuXHQgIHJldHVybiBgTSR7eDF9LCR7eTF9IEwke3gyfSwke3kyfWA7XG5cdH1cblxuXHRkaWFnVXAuZW50ZXIoKVxuXHQgIC5hcHBlbmQoJ3BhdGgnKVxuXHQgIC5hdHRyKCdjbGFzcycsICdkaWFnb25hbFVwJylcblx0ICAuYXR0cignZCcsIGQgPT4gdG9QYXRoKGRUb0xpbmUoZCkpKVxuXHQgIC5zdHlsZSgnc3Ryb2tlJywgbWFya0NvbG9yKVxuXHQgIC5zdHlsZSgnZmlsbCcsIG1hcmtDb2xvcik7XG5cblx0Ly8gYWRkIGRpYWdvbmFsIGRvd25cblx0Y29uc3QgZGlhZ0Rvd24gPSBzbGljZS5zZWxlY3RBbGwoJy5kaWFnb25hbERvd24nKS5kYXRhKGRhdGFCeVR5cGUuZGlhZ29uYWxEb3duIHx8IFtdKTtcblxuXHRkaWFnRG93bi5lbnRlcigpXG5cdCAgLmFwcGVuZCgncGF0aCcpXG5cdCAgLmF0dHIoJ2NsYXNzJywgJ2RpYWdvbmFsRG93bicpXG5cdCAgLmF0dHIoJ2QnLCBkID0+IHRvUGF0aChkVG9MaW5lKGQpKSlcblx0ICAuc3R5bGUoJ3N0cm9rZScsIG1hcmtDb2xvcilcblx0ICAuc3R5bGUoJ2ZpbGwnLCBtYXJrQ29sb3IpO1xuXG5cblx0Ly8gYWRkIFggbWFya3Ncblx0Y29uc3QgeE1hcmtzID0gc2xpY2Uuc2VsZWN0QWxsKCcueCcpLmRhdGEoZGF0YUJ5VHlwZS54IHx8IFtdKTtcblxuXHRjb25zdCB4TWFya0dzID0geE1hcmtzLmVudGVyKClcblx0ICAuYXBwZW5kKCdnJylcblx0ICAuYXR0cignY2xhc3MnLCAneCcpO1xuXG5cdHhNYXJrR3MuYXBwZW5kKCdwYXRoJylcblx0ICAuYXR0cignZCcsIGQgPT4gdG9QYXRoKGRUb0xpbmUoZCkpKVxuXHQgIC5zdHlsZSgnc3Ryb2tlJywgbWFya0NvbG9yKVxuXHQgIC5zdHlsZSgnZmlsbCcsIG1hcmtDb2xvcik7XG5cblx0eE1hcmtHcy5hcHBlbmQoJ3BhdGgnKVxuXHQgIC5hdHRyKCdkJywgZCA9PiB0b1BhdGgoZFRvTGluZShkLCAneTInLCAneTEnKSkpXG5cdCAgLnN0eWxlKCdzdHJva2UnLCBtYXJrQ29sb3IpXG5cdCAgLnN0eWxlKCdmaWxsJywgbWFya0NvbG9yKTtcblxuXHQvLyBhZGQgWCBtYXJrc1xuXHRjb25zdCBhcnJvd01hcmtzID0gc2xpY2Uuc2VsZWN0QWxsKCcuYXJyb3cnKS5kYXRhKGRhdGFCeVR5cGUuYXJyb3cgfHwgW10pO1xuXG5cdGNvbnN0IGFycm93TWFya0dzID0gYXJyb3dNYXJrcy5lbnRlcigpXG5cdCAgLmFwcGVuZCgnZycpXG5cdCAgLmF0dHIoJ2NsYXNzJywgJ2Fycm93Jyk7XG5cblx0YXJyb3dNYXJrR3MuYXBwZW5kKCdwYXRoJylcblx0ICAuYXR0cignZCcsIGQgPT4gdG9QYXRoKGRUb0xpbmUoZCwgJ3kxJywgJ3lNaWQnKSkpXG5cdCAgLnN0eWxlKCdzdHJva2UnLCBtYXJrQ29sb3IpXG5cdCAgLnN0eWxlKCdmaWxsJywgbWFya0NvbG9yKTtcblxuXHRhcnJvd01hcmtHcy5hcHBlbmQoJ3BhdGgnKVxuXHQgIC5hdHRyKCdkJywgZCA9PiB0b1BhdGgoZFRvTGluZShkLCAneTInLCAneU1pZCcpKSlcblx0ICAuc3R5bGUoJ3N0cm9rZScsIG1hcmtDb2xvcilcblx0ICAuc3R5bGUoJ2ZpbGwnLCBtYXJrQ29sb3IpO1xufVxuXG5nZW5lcmF0ZU1hbmRhbGEoKTtcblxuZDMuc2VsZWN0KCcjbWFrZS1tYW5kYWxhJykub24oJ2NsaWNrJywgZ2VuZXJhdGVNYW5kYWxhKTtcblxuIl19 |
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
<!DOCTYPE html> | |
<title>Mandala Generator with D3 and SVG use</title> | |
<style> | |
button { | |
font-size: 30px; | |
margin-bottom: 10px; | |
width: 600px; | |
} | |
</style> | |
<body> | |
<button id="make-mandala">Make a new Mandala</button> | |
<div id="vis-container"></div> | |
<script src='https://d3js.org/d3.v4.min.js'></script> | |
<script src='dist.js'></script> | |
</body> |
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
// mark types | |
// const markTypes = ['diagonalUp', 'diagonalDown', 'x', 'arc', 'point']; // 'square']; | |
const markTypes = ['x', 'arrow', 'arc', 'point']; // 'square']; | |
const animate = true; | |
const numMarks = 30; | |
// outer svg dimensions | |
const width = 600; | |
const height = 600; | |
// padding around the chart | |
const padding = { | |
top: 20, | |
right: 20, | |
bottom: 20, | |
left: 20, | |
}; | |
// inner chart dimensions, where the dots are plotted | |
const plotAreaWidth = width - padding.left - padding.right; | |
const plotAreaHeight = height - padding.top - padding.bottom; | |
// size of an individual slice | |
const numSlices = 32; | |
const sliceHeight = plotAreaHeight / 2; | |
const sliceAngle = (2 * Math.PI) / numSlices; | |
const arc = d3.arc(); | |
function generateMandala() { | |
// generate random data | |
let cumulativeSize = 0; | |
let prevType; | |
const data = d3.range(numMarks).map((d, i) => { | |
let type; | |
let validType; | |
do { | |
validType = true; | |
type = markTypes[Math.floor(Math.random() * markTypes.length)]; | |
if (i > 5 && type === 'arrow') { | |
validType = false; | |
} | |
} while (!validType); | |
// type = 'arrow'; | |
prevType = type; | |
let item; | |
if (type === 'point') { | |
const size = Math.ceil(Math.random() * 20) + 10; | |
item = { | |
type, | |
r: size / 5, | |
size, | |
cumulativeSize, | |
y: cumulativeSize + (size / 2), | |
filled: Math.random() > 0.3, | |
}; | |
} else if (type === 'arc') { | |
const size = Math.ceil(Math.random() * 20) + 2; | |
item = { | |
type, | |
thickness: Math.round(size / 4), | |
size, | |
cumulativeSize, | |
y: cumulativeSize + (size / 2), | |
}; | |
} else if (type === 'diagonalUp') { | |
const size = Math.ceil(Math.random() * 10) + 3; | |
item = { | |
type, | |
size, | |
cumulativeSize, | |
y1: cumulativeSize, | |
y2: cumulativeSize + size, | |
}; | |
} else if (type === 'diagonalDown') { | |
const size = Math.ceil(Math.random() * 10) + 3; | |
item = { | |
type, | |
size, | |
cumulativeSize, | |
y1: cumulativeSize + size, | |
y2: cumulativeSize, | |
}; | |
} else if (type === 'x') { | |
const size = Math.ceil(Math.random() * 10) + 3; | |
item = { | |
type, | |
size, | |
cumulativeSize, | |
y1: cumulativeSize + size, | |
y2: cumulativeSize, | |
}; | |
} else if (type === 'arrow') { | |
const size = Math.ceil(Math.random() * 10) + 3; | |
item = { | |
type, | |
size, | |
cumulativeSize, | |
y1: cumulativeSize + size, | |
yMid: cumulativeSize + (size / 2), | |
y2: cumulativeSize, | |
}; | |
} else { | |
item = { size: 0 }; | |
} | |
item.id = i; | |
cumulativeSize += item.size; | |
return item; | |
}); | |
const dataByType = d3.nest().key(d => d.type).object(data); | |
// initialize scales | |
const yScale = d3.scaleLinear().domain([0, cumulativeSize]).range([sliceHeight, 0]); | |
const rScale = d3.scaleLinear().domain([0, cumulativeSize]).range([0, sliceHeight]); | |
// select the root container where the chart will be added | |
const container = d3.select('#vis-container'); | |
// clear any old contents | |
container.selectAll('*').remove(); | |
// initialize main SVG | |
const svg = container.append('svg') | |
.attr('width', width) | |
.attr('height', height); | |
const bgHue = Math.floor(Math.random() * 360); | |
const bgSaturation = (Math.random() * 0.3) + 0.7; | |
const bgLightness = (Math.random() * 0.15) + 0.05; | |
const bg = d3.hsl(bgHue, bgSaturation, bgLightness); | |
d3.select('body').style('background'); | |
// draw the background | |
svg.append('rect') | |
.attr('class', 'mandala-bg') | |
.attr('width', width) | |
.attr('height', height) | |
.style('fill', bg); | |
// the main <g> where all the chart content goes inside | |
const g = svg.append('g') | |
.attr('transform', `translate(${padding.left} ${padding.top})`); | |
if (animate) { | |
g.transition() | |
.duration(2500) | |
.attrTween('transform', () => | |
d3.interpolateString(`translate(${padding.left} ${padding.top}) rotate(0 ${plotAreaWidth / 2} ${plotAreaHeight / 2})`, | |
`translate(${padding.left} ${padding.top}) rotate(360 ${plotAreaWidth / 2} ${plotAreaHeight / 2})`)); | |
} | |
const defs = g.append('defs'); | |
// clip path for slices disabled to allow some slight overlap for things like arcs | |
// add the slice as a clip path | |
// defs.append('clipPath') | |
// .attr('id', 'slice-clip') | |
// .append('path') | |
// .attr('transform', `translate(0 ${sliceHeight})`) | |
// .attr('d', arc({ | |
// innerRadius: 0, | |
// outerRadius: sliceHeight, | |
// startAngle: -(sliceAngle / 2), | |
// endAngle: sliceAngle / 2, | |
// })) | |
// .style('fill', 'tomato') | |
// .style('stroke', 'tomato') | |
// .style('stroke-width', 5); | |
// radial gradient for background | |
const mandalaBgGrad = defs.append('radialGradient') | |
.attr('id', 'bg-shading') | |
.attr('gradientUnits', 'userSpaceOnUse'); | |
mandalaBgGrad.append('stop') | |
.attr('offset', '0%') | |
.attr('stop-color', '#000') | |
.attr('stop-opacity', 0.0); | |
mandalaBgGrad.append('stop') | |
.attr('offset', '100%') | |
.attr('stop-color', '#000') | |
.attr('stop-opacity', 0.2); | |
svg.insert('rect', 'g') | |
.attr('class', 'mandala-bg-shading') | |
.attr('width', width) | |
.attr('height', height) | |
.style('fill', 'url(#bg-shading)'); | |
// add in a big clip for all the marks | |
const marksClip = defs.append('clipPath') | |
.attr('id', 'marks-clip') | |
.append('circle') | |
.attr('cx', plotAreaWidth / 2) | |
.attr('cy', plotAreaHeight / 2) | |
.attr('r', 0) | |
.style('fill', '#fff'); | |
if (animate) { | |
marksClip.transition() | |
.ease(d3.easeLinear) | |
.duration(2000) | |
.attr('r', (plotAreaHeight / 2) + 5); | |
} else { | |
marksClip | |
.attr('r', (plotAreaHeight / 2) + 5); | |
} | |
const gSlices = g.append('g') | |
.attr('class', 'slices-group') | |
.attr('clip-path', 'url(#marks-clip)'); | |
// create the group to be repeated | |
const slice = gSlices.append('g') | |
.attr('id', 'ref-slice') | |
.attr('class', 'slice') | |
.attr('transform', `translate(${plotAreaWidth / 2} 0)`) | |
.attr('clip-path', 'url(#slice-clip)'); | |
// add in copies of this slice | |
const copySlices = d3.range(numSlices - 1).map((d, i) => ({ | |
id: i + 1, | |
href: '#ref-slice', | |
transform: `rotate(${(i + 1) * sliceAngle * (180 / Math.PI)} ${plotAreaWidth / 2} ${sliceHeight})`, | |
})); | |
const sliceBinding = gSlices.selectAll('copy-slice').data(copySlices); | |
sliceBinding.enter().append('use') | |
.attr('xlink:href', d => d.href) | |
.attr('transform', d => d.transform); | |
// build up the slice | |
slice.append('path') | |
.attr('class', 'slice-bg') | |
.attr('transform', `translate(0 ${sliceHeight})`) | |
.attr('d', arc({ | |
innerRadius: 0, | |
outerRadius: sliceHeight, | |
startAngle: -(sliceAngle / 2), | |
endAngle: sliceAngle / 2, | |
})) | |
.style('fill', 'none') | |
.style('stroke', 'tomato') | |
.style('opacity', 0.0); | |
const markColor = '#fff'; | |
// add points to the slice | |
const points = slice.selectAll('.point').data(dataByType.point || []); | |
points.enter() | |
.append('circle') | |
.attr('class', 'point') | |
.attr('r', d => d.r) | |
.attr('cx', 0) | |
.attr('cy', d => yScale(d.y)) | |
.style('fill', d => (d.filled ? markColor : 'none')) | |
.style('stroke', d => (d.filled ? 'none' : markColor)); | |
// add arcs | |
const arcs = slice.selectAll('.arc').data(dataByType.arc || []); | |
const interiorArc = d3.arc() | |
.innerRadius(d => rScale(d.y - d.thickness)) | |
.outerRadius(d => rScale(d.y)) | |
.startAngle((-sliceAngle / 2) - 0.1) // slight padding to ensure overlap | |
.endAngle((sliceAngle / 2) + 0.1); | |
arcs.enter() | |
.append('path') | |
.attr('transform', `translate(0 ${sliceHeight})`) | |
.attr('class', 'arc') | |
.attr('d', interiorArc) | |
.style('fill', markColor); | |
// add diagonal line up | |
const diagUp = slice.selectAll('.diagonalUp').data(dataByType.diagonalUp || []); | |
function dToLine(d, y1Key = 'y1', y2Key = 'y2') { | |
const y1 = yScale(d[y1Key]); | |
const y2 = yScale(d[y2Key]); | |
const x1 = (sliceHeight - y1) * Math.tan(-sliceAngle / 2); | |
const x2 = (sliceHeight - y2) * Math.tan(sliceAngle / 2); | |
return { | |
x1, | |
x2, | |
y1, | |
y2, | |
}; | |
} | |
function toPath({ x1, x2, y1, y2 }) { | |
return `M${x1},${y1} L${x2},${y2}`; | |
} | |
diagUp.enter() | |
.append('path') | |
.attr('class', 'diagonalUp') | |
.attr('d', d => toPath(dToLine(d))) | |
.style('stroke', markColor) | |
.style('fill', markColor); | |
// add diagonal down | |
const diagDown = slice.selectAll('.diagonalDown').data(dataByType.diagonalDown || []); | |
diagDown.enter() | |
.append('path') | |
.attr('class', 'diagonalDown') | |
.attr('d', d => toPath(dToLine(d))) | |
.style('stroke', markColor) | |
.style('fill', markColor); | |
// add X marks | |
const xMarks = slice.selectAll('.x').data(dataByType.x || []); | |
const xMarkGs = xMarks.enter() | |
.append('g') | |
.attr('class', 'x'); | |
xMarkGs.append('path') | |
.attr('d', d => toPath(dToLine(d))) | |
.style('stroke', markColor) | |
.style('fill', markColor); | |
xMarkGs.append('path') | |
.attr('d', d => toPath(dToLine(d, 'y2', 'y1'))) | |
.style('stroke', markColor) | |
.style('fill', markColor); | |
// add X marks | |
const arrowMarks = slice.selectAll('.arrow').data(dataByType.arrow || []); | |
const arrowMarkGs = arrowMarks.enter() | |
.append('g') | |
.attr('class', 'arrow'); | |
arrowMarkGs.append('path') | |
.attr('d', d => toPath(dToLine(d, 'y1', 'yMid'))) | |
.style('stroke', markColor) | |
.style('fill', markColor); | |
arrowMarkGs.append('path') | |
.attr('d', d => toPath(dToLine(d, 'y2', 'yMid'))) | |
.style('stroke', markColor) | |
.style('fill', markColor); | |
} | |
generateMandala(); | |
d3.select('#make-mandala').on('click', generateMandala); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment