Skip to content

Instantly share code, notes, and snippets.

@pbeshai
Last active November 10, 2023 09:46
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save pbeshai/2395deb8b40dcdfdb6e72ee51a6df251 to your computer and use it in GitHub Desktop.
Save pbeshai/2395deb8b40dcdfdb6e72ee51a6df251 to your computer and use it in GitHub Desktop.
Mandala Generator with D3 and SVG use
license: mit
height: 720
border: no

Mandala Generator with D3 and SVG use

A playful demonstration of using svg's <use> tag to make a mandala.

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
<!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>
// 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