Skip to content
Advertisement

Insert circular text into sections in SVG using Javascript

It’s me again with my “Wheel of life” thing. But since last time i’ve got many steps further. I actually arrived at the final step, adding text in sections.

Here is the code I have so far (open in full screen):

d3.select('#step').on('click',function(){
    sections = document.getElementById("sections").value;
    deuxiemeEtape(sections);
});



function deuxiemeEtape(sections){
    
    var form=d3.select("#form2")
    form.append('hr');
    form.append('p')
        .html('Titres des petites sections')
        .style('text-decoration','underline')

       
    for (i=1;i<parseInt(sections)+1;i++){
        form.append('label')
            .html("Nom de la petite section "+i);
        form.append('input')
            .attr('type','text')
            .attr('id','ps'+i);
    }
    form.append('hr');
    form.append('p')
        .html('Titres des sections')
        .style('text-decoration','underline');
    
    
    for (i=1;i<(parseInt(sections)+2)/2;i++){
        form.append('label')
            .html("Nom de la section "+i);
        form.append('input')
            .attr('type','text')
            .attr('id','gs'+i);
    }
    d3.select("#circles").attr('disabled','');
    d3.select("#sections").attr('disabled','');
    d3.select("#step").attr('disabled','');
    form.append('button')
        .attr("id","create")
        .html("Créer")
        .on('click', onClickButton);
    form.append('button')
        .attr("id","reset")
        .html("Reset")
        .on('click',reloadPage);
    
}

function reloadPage(){
    window.location.reload();
}




const createSvg = (circles, sections) => {
    const svg = d3.select('#canvas');
    const width = parseInt(svg.attr('width'));
    const height = parseInt(svg.attr('height'));
    svg.selectAll('g').remove();
    const g = svg.append('g')
        .attr('transform', `translate(${width / 2},${height / 2})`);
    let i;
    for (i = parseInt(circles)+1; i >= 0; i--)
    {
        if (i==circles){
            g.append('circle')
            .attr('r', 40 + i * 30)
            .style('fill', 'blue')
            .style('stroke', 'black');
        }else if (i==parseInt(circles)+1){
            g.append('circle')
            .attr('r', 40 + i * 30)
            .style('fill', 'red')
            .style('stroke', 'black');
        
        } else {
            g.append('circle')
            .attr('r', 40 + i * 30)
            .style('fill', 'white')
            .style('stroke', 'black');
        }
        
    }
    
   
    const angle = Math.PI * 2 / sections;
    let points = "";
    for (i = 0; i <= sections; i++) {
        if(i%2==0){
          //Regular sections
             radius = (circles) * 30 + 70;
        } else {
          //Small sections
             radius = (circles-1) * 30 + 70;
        }
        
        const x = radius * Math.sin(angle * i);
        const y = radius * -Math.cos(angle * i);
        g.append('line')
            .attr('x1', 0)
            .attr('y1', 0)
            .attr('x2', x)
            .attr('y2', y)
            .style('stroke', 'black');
        points += ` ${x},${y}`;
    }

     // Don't mind this, it's WIP
    function onPolygonClick () {
        const x = d3.event.layerX - width / 2;
        const y = d3.event.layerY - height / 2;
        const radius = Math.hypot(x, y);
        let clickAngle = Math.atan2(x, -y);
        if (clickAngle < 0){
            clickAngle = Math.PI * 2 + clickAngle;
        }
            

        const circle = Math.abs(Math.ceil((radius - 40) / 30));    
        const sector = Math.floor(clickAngle / angle)
        alert("Cliqué sur cercle "+circle+" / secteur "+sector+"");
    }

    // Don't mind this, it's WIP
    g.append('polygon')
        .attr('points', points)
        .style('fill', 'white')
        .style('stroke', 'black')
        .style('fill-opacity', 0.01)
        .style('cursor', 'pointer')
        .style('display','none') 
        .on('click', onPolygonClick);
}



function onClickButton () {
    const circles = d3.select('#circles').node().value;
    const sections = d3.select('#sections').node().value;
    createSvg(circles, sections);
}
body{
    margin:0;
    padding:0;
    background-color: rgb(78, 98, 112);
}

#formulaire,#form2{
    width:9.3%
}

input{
    border-radius: 3px;
    margin:1em 0;
}

svg{
    border:3px black solid
}
<!DOCTYPE html>
<html lang="fr">

<head>
    <meta charset="utf-8">
    <title>SVG</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
    <div>
        <div id="contenu">
            <div id="formulaire">
                <label>Number of circles</label>
                <input type="number" min="1" id="circles" />
                <br />
                <label>Number of sections</label>
                <input type="number" min="1" id="sections" />
                <button id="step">Suivant</button>
            </div>
            <div id="form2">
                <!-- Insert inputs here -->
            </div>
        </div>
        <div>
            <svg id="canvas" height="750" width="1700">
            </svg>
        </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <script src="script.js"></script>
</body>

</html>

So as you can see, the inputs to get the text are added below the previous one. I need to add the text of the small sections in the blue circle, and the regular sections in the red circles ! I can’t figure how to do this.

Here is an exemple of how it should look with the text : enter image description here

Thank you for reading this ! And thank you for answering if you do !

Advertisement

Answer

I could achieve it to append text but not making text center aligned exactly.

enter image description here

d3.select('#step').on('click', function() {
  sections = document.getElementById("sections").value;
  deuxiemeEtape(sections);
});



function deuxiemeEtape(sections) {

  var form = d3.select("#form2")
  form.append('hr');
  form.append('p')
    .html('Titres des petites sections')
    .style('text-decoration', 'underline')


  for (i = 1; i < parseInt(sections) + 1; i++) {
    form.append('label')
      .html("Nom de la petite section " + i);
    form.append('input')
      .attr('class', 'sub-sections')
      .attr('type', 'text')
      .attr('id', 'ps' + i);
  }
  form.append('hr');
  form.append('p')
    .html('Titres des sections')
    .style('text-decoration', 'underline');


  for (i = 1; i < (parseInt(sections) + 2) / 2; i++) {
    form.append('label')
      .html("Nom de la section " + i);
    form.append('input')
      .attr('class', 'sections')
      .attr('type', 'text')
      .attr('id', 'gs' + i);
  }
  d3.select("#circles").attr('disabled', '');
  d3.select("#sections").attr('disabled', '');
  d3.select("#step").attr('disabled', '');
  form.append('button')
    .attr("id", "create")
    .html("Créer")
    .on('click', onClickButton);
  form.append('button')
    .attr("id", "reset")
    .html("Reset")
    .on('click', reloadPage);

}

function reloadPage() {
  window.location.reload();
}


const createSvg = (circles, sections) => {
  const svg = d3.select('#canvas');
  const width = parseInt(svg.attr('width'));
  const height = parseInt(svg.attr('height'));
  svg.selectAll('g').remove();
  const g = svg.append('g')
    .attr('transform', `translate(${width / 2},${height / 2})`);

  const archThickness = 30;
  const fullCircle = 2 * 3.14;

  //adding inner sections
  const arcSizeSmallSections = fullCircle / sections;
  const subSectionInputs = document.querySelectorAll('.sub-sections');
  for (let i = 0; i < circles; i++) {
    for (let j = 0; j < sections; j++) {
      if (i + 1 === circles) {
        appendArcs(g, i, j, archThickness, arcSizeSmallSections, 'blue', subSectionInputs[j].value);
      } else {
        appendArcs(g, i, j, archThickness, arcSizeSmallSections, 'white', i + 1);
      }

    }
  }

  // adding outer sections
  const mergeSplits = Math.ceil(sections / 2);
  const arcSizeSections = fullCircle / sections;
  const sectionInputs = document.querySelectorAll('.sections');
  const endAngle = 2 * arcSizeSections;
  for (let j = 0; j < mergeSplits; j++) {
    g.append("path")
      .attr("d", d3.arc()
        .innerRadius(circles * archThickness)
        .outerRadius((circles + 1) * archThickness)
        .startAngle(j * endAngle)
        .endAngle(((j + 1) * endAngle) > fullCircle ? fullCircle : (j + 1) * endAngle)
      )
      .attr("id", () => 'section' + circles + j)
      .attr('stroke', 'black')
      .attr('fill', 'red');
    g.append("text")
      .attr("class", "monthText")
      .attr("dy", 22)
      .append("textPath")
      .attr("startOffset", "22%")
      .style("text-anchor", "middle")
      .attr("xlink:href", () => "#section" + circles + j)
      .text(() => sectionInputs[j].value);
  }
}


function appendArcs(parent, i, j, archThickness, arcSize, color, text) {

  parent
    .append("path")
    .attr("d", d3.arc()
      .innerRadius(i * archThickness)
      .outerRadius((i + 1) * archThickness)
      .startAngle(j * arcSize)
      .endAngle((j + 1) * arcSize)
    )
    .attr("id", () => 'section' + i + j)
    .attr('stroke', 'black')
    .attr('fill', color);
  if (text) {
    parent.append("text")
      .attr("class", "monthText")
      .attr("dy", 22)
      .style("text-anchor", "middle")
      .append("textPath")
      .attr("startOffset", "22%")
      .attr("xlink:href", () => "#section" + i + j)
      .text(() => text);
  }

}



function onClickButton() {
  const circles = d3.select('#circles').node().value;
  const sections = d3.select('#sections').node().value;
  createSvg(parseInt(circles), parseInt(sections));
}
body {
  margin: 0;
  padding: 0;
  background-color: rgb(78, 98, 112);
}

#formulaire,
#form2 {
  width: 9.3%
}

input {
  border-radius: 3px;
  margin: 1em 0;
}

svg {
  border: 3px black solid
}

.monthText {
  fill: #161414;
  font-size: 22px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div>
  <div id="contenu">
    <div id="formulaire">
      <label>Number of circles</label>
      <input type="number" min="1" id="circles" />
      <br />
      <label>Number of sections</label>
      <input type="number" min="1" id="sections" />
      <button id="step">Suivant</button>
    </div>
    <div id="form2">
      <!-- Insert inputs here -->
    </div>
  </div>
  <div>
    <svg id="canvas" height="750" width="1700">
            </svg>
  </div>
</div>

Added support for dynamic sub sections.

d3.select('#step').on('click', function() {
  sections = document.getElementById("sections").value;
  deuxiemeEtape(sections);
});



function deuxiemeEtape(sections) {

  var form = d3.select("#form2")
  form.append('hr');
  form.append('p').html('Titres des sections').style('text-decoration', 'underline');
  for (i = 1; i <= parseInt(sections); i++) {
    let ind = 0,
      row = i;

    const secGroup = form.append('div').attr('class', 'section');
    const sec = secGroup.append('div').attr('class', 'section-info');
    sec.append('label').html("Nom de la section " + i);
    sec.append('input').attr('type', 'text');
    sec.append('button').attr('type', 'button').attr('class', 'ms-2').text('Add Sub section')
      .on('click', () => {
        const subSecGroup = subSections.append('div').attr('class', 'sub-section');
        subSecGroup.append('label').html("Nom de la petite section " + row + ++ind);
        subSecGroup.append('input').attr('type', 'text');
        subSecGroup.append('button').attr('type', 'button').attr('class', 'ms-2')
          .text('Remove').on('click', () => {
            subSecGroup.remove();
          })
      });

    const subSections = secGroup.append('div').attr('class', 'ms-5 my-3 sub-sections');
    const subSecGroup = subSections.append('div').attr('class', 'sub-section');

    subSecGroup.append('label').html("Nom de la petite section " + i + 0);
    subSecGroup.append('input').attr('type', 'text');
  }


  d3.select("#circles").attr('disabled', '');
  d3.select("#sections").attr('disabled', '');
  d3.select("#step").attr('disabled', '');
  form.append('button')
    .attr("id", "create")
    .html("Créer")
    .on('click', onClickButton);
  form.append('button')
    .attr("id", "reset")
    .html("Reset")
    .on('click', reloadPage);

}

function reloadPage() {
  window.location.reload();
}


const createSvg = (circles) => {
  const svg = d3.select('#canvas');
  const width = parseInt(svg.attr('width'));
  const height = parseInt(svg.attr('height'));
  svg.selectAll('g').remove();
  const g = svg.append('g')
    .attr('transform', `translate(${width / 2},${height / 2})`);

  const archThickness = 30;
  const fullCircle = 2 * 3.14;

  //adding inner sections
  const subSectionInputs = document.querySelectorAll('.sub-section input');
  const arcSizeSmallSections = fullCircle / subSectionInputs.length;
  for (let i = 0; i < circles; i++) {
    for (let j = 0; j < subSectionInputs.length; j++) {
      if (i + 1 === circles) {
        appendArcs(g, i, j, archThickness, arcSizeSmallSections, 'blue', subSectionInputs[j].value);
      } else {
        appendArcs(g, i, j, archThickness, arcSizeSmallSections, 'white', i + 1);
      }

    }
  }

  // adding outer sections
  const sections = document.querySelectorAll('.section');
  let prevAngle = 0;
  for (let j = 0; j < sections.length; j++) {
    const subSections = sections[j].querySelectorAll('.sub-section input');
    const endAngle = prevAngle + (subSections.length * arcSizeSmallSections);
    const text = sections[j].querySelector('.section-info input').value;
    g.append("path")
      .attr("d", d3.arc()
        .innerRadius(circles * archThickness)
        .outerRadius((circles + 1) * archThickness)
        .startAngle(prevAngle)
        .endAngle(endAngle)
      )
      .attr("id", () => 'section' + circles + j)
      .attr('stroke', 'black')
      .attr('fill', 'red');
    g.append("text")
      .attr("class", "monthText")
      .attr("dy", 22)
      .append("textPath")
      .attr("startOffset", "22%")
      .style("text-anchor", "middle")
      .attr("xlink:href", () => "#section" + circles + j)
      .text(() => text);

    prevAngle = endAngle;
  }
}


function appendArcs(parent, i, j, archThickness, arcSize, color, text) {

  parent
    .append("path")
    .attr("d", d3.arc()
      .innerRadius(i * archThickness)
      .outerRadius((i + 1) * archThickness)
      .startAngle(j * arcSize)
      .endAngle((j + 1) * arcSize)
    )
    .attr("id", () => 'section' + i + j)
    .attr('stroke', 'black')
    .attr('fill', color);
  if (text) {
    parent.append("text")
      .attr("class", "monthText")
      .attr("dy", 22)
      .style("text-anchor", "middle")
      .append("textPath")
      .attr("startOffset", "22%")
      .attr("xlink:href", () => "#section" + i + j)
      .text(() => text);
  }

}



function onClickButton() {
  const circles = d3.select('#circles').node().value;
  const sections = d3.select('#sections').node().value;
  createSvg(parseInt(circles), parseInt(sections));
}
body {
  margin: 0;
  padding: 0;
  background-color: rgb(78, 98, 112);
}

input {
  border-radius: 3px;
  margin: 1em 0;
}

svg {
  border: 3px black solid
}

.monthText {
  fill: #161414;
  font-size: 22px;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
  <div id="contenu" class="row">
    <div id="formulaire" class="col-6">
      <label>Number of circles</label>
      <input type="number" min="1" id="circles" />
    </div>
    <div class="col-6">
      <label>Number of sections</label>
      <input type="number" min="1" id="sections" />
    </div>
  </div>
  <div class="row my-2"> <button type="button" class="btn btn-primary col-2 m-auto" id="step">Suivant</button> </div>
  <div id="form2">
    <!-- Insert inputs here -->
  </div>
  <div>
    <svg id="canvas" height="750" width="1700"></svg>
  </div>
</div>
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement