Files
freeCodeCamp/curriculum/challenges/english/04-data-visualization/d3-dashboard/part-140.md
Oliver Eyton-Williams 0bd52f8bd1 Feat: add new Markdown parser (#39800)
and change all the challenges to new `md` format.
2020-11-27 10:02:05 -08:00

13 KiB

id, title, challengeType
id title challengeType
5d8a4cfbe6b6180ed9a1ca6c Part 140 0

--description--

There's a problem, each time you hover a label it adds all the elements to the container again. If you empty the container at the top of the function, it will redraw them where they need to be.

Go back to the top of the function and use d3.select to select the .dashboard element and chain the html function to it with an empty string as it parameter. Empty means no spaces.

--hints--

test-text

const script = $('.dashboard').siblings('script')[1].innerHTML;
assert(/d3\.select\(('|"|`)\.dashboard\1\)\.html\(('|"|`)\2\)/g.test(script));

--seed--

--before-user-code--

<!DOCTYPE html>
<html>
  <head>
    <title>D3 Dashboard</title>
    <style>
      body {
        background-color: #ccc;
        padding: 100px 10px;
      }

      .dashboard {
        width: 980px;
        height: 500px;
        background-color: white;
        box-shadow: 5px 5px 5px 5px #888;
        margin: auto;
        display: flex;
        align-items: center;
      }
    </style>
  </head>

  <body>
    <div class="dashboard"></div>
  </body>
</html>

--seed-contents--

<script>
  const data = [ 
    { year: 2012, followers: { twitter: 2594, tumblr:  401, instagram:   83 }},
    { year: 2013, followers: { twitter: 3049, tumblr:  440, instagram:  192 }},
    { year: 2014, followers: { twitter: 3511, tumblr:  415, instagram:  511 }},
    { year: 2015, followers: { twitter: 3619, tumblr:  492, instagram: 1014 }},
    { year: 2016, followers: { twitter: 4046, tumblr:  543, instagram: 2066 }},
    { year: 2017, followers: { twitter: 3991, tumblr:  701, instagram: 3032 }},
    { year: 2018, followers: { twitter: 3512, tumblr: 1522, instagram: 4512 }},
    { year: 2019, followers: { twitter: 3274, tumblr: 1989, instagram: 4715 }},
    { year: 2020, followers: { twitter: 2845, tumblr: 2040, instagram: 4801 }}
  ];
</script>
<script>
  function drawDashboard(year) {



    const index = data.findIndex(d => d.year === year);

    const svgMargin = 70,
      svgWidth = 700,
      svgHeight = 500,
      twitterColor = '#7cd9d1',
      tumblrColor = '#f6dd71',
      instagramColor = '#fd9b98';

    const lineGraph = d3.select('.dashboard')
      .append('svg')
      .attr('width', svgWidth)
      .attr('height', svgHeight);

    const yScale = d3.scaleLinear()
      .domain([0, 5000])
      .range([svgHeight - svgMargin, svgMargin]);

    const xScale = d3.scaleLinear()
      .domain([2012, 2020])
      .range([svgMargin, svgWidth - svgMargin]);

    const yAxis = d3.axisLeft(yScale)
      .ticks(6, '~s');

    const xAxis = d3.axisBottom(xScale)
      .tickFormat(d3.format(''))
      .tickPadding(10);

    lineGraph.append('g')
      .call(yAxis)
      .attr('transform', `translate(${svgMargin}, 0)`)
      .style('font', '10px verdana');

    lineGraph.append('g')
      .call(xAxis)
      .attr('transform', `translate(0, ${svgHeight - svgMargin})`)
      .selectAll('text')
      .style('transform', 'translate(-12px, 0) rotate(-50deg)')
      .style('text-anchor', 'end')
      .style('cursor', 'pointer')
      .style('font', '10px verdana')
      .on('mouseover', d => drawDashboard(d));

    const twitterLine = d3.line()
      .x(d => xScale(d.year))
      .y(d => yScale(d.followers.twitter));

    lineGraph.append('path')
      .attr('d', twitterLine(data))
      .attr('stroke', twitterColor)
      .attr('stroke-width', 3)
      .attr('fill', 'transparent');

    const tumblrLine = d3.line()
      .x(d => xScale(d.year))
      .y(d => yScale(d.followers.tumblr));

    lineGraph.append('path')
      .attr('d', tumblrLine(data))
      .attr('stroke', tumblrColor)
      .attr('stroke-width', 3)
      .attr('fill', 'transparent');

    const instagramLine = d3.line()
      .x(d => xScale(d.year))
      .y(d => yScale(d.followers.instagram));

    lineGraph.append('path')
      .attr('d', instagramLine(data))
      .attr('stroke', instagramColor)
      .attr('stroke-width', 3)
      .attr('fill', 'transparent');
      
    lineGraph.selectAll('twitter-circles')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', d => xScale(d.year))
      .attr('cy', d => yScale(d.followers.twitter))
      .attr('r', 6)
      .attr('fill', 'white')
      .attr('stroke', twitterColor)
      .style('cursor', 'pointer')

    lineGraph.selectAll('tumblr-circles')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', d => xScale(d.year))
      .attr('cy', d => yScale(d.followers.tumblr))
      .attr('r', 6)
      .attr('fill', 'white')
      .attr('stroke', tumblrColor)
      .style('cursor', 'pointer')

    lineGraph.selectAll('instagram-circles')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', d => xScale(d.year))
      .attr('cy', d => yScale(d.followers.instagram))
      .attr('r', 6)
      .attr('fill', 'white')
      .attr('stroke', instagramColor)
      .style('cursor', 'pointer')

    const rightDashboard = d3.select('.dashboard')
      .append('div');

    const pieGraph = rightDashboard.append('svg')
      .attr('width', 200)
      .attr('height', 200)
      .style('position', 'relative')
      .style('left', '20px');

    const pieArc = d3.arc()
      .outerRadius(100)
      .innerRadius(0);

    const pieColors = d3.scaleOrdinal()  
      .domain(data[index].followers)
      .range([twitterColor, tumblrColor, instagramColor]);

    const pie = d3.pie()
      .value(d => d.value);
      
    const pieGraphData = pieGraph.selectAll('pieSlices')
      .data(pie(d3.entries(data[index].followers)))
      .enter()
      .append('g')
      .attr('transform', 'translate(100, 100)');

    pieGraphData.append('path')
      .attr('d', pieArc)
      .attr('fill', d => pieColors(d.data.key))
      .attr('stroke', 'white')
      .attr('stroke-width', 2);

    pieGraphData.append('text')
      .text(d => {
        const values = d3.values(data[index].followers);
        const sum = d3.sum(values);
        const percent = d.data.value/sum;
        return `${ Math.round(percent*100) }%`;
      })
      .attr('transform', d => `translate(${pieArc.centroid(d)})`)
      .style('text-anchor', 'middle')
      .style('font', '10px verdana');

    const legend = rightDashboard.append('table')
      .attr('width', 200)
      .attr('height', 120)
      .style('font', '12px verdana')
      .style('position', 'relative')
      .style('top', '30px');

    const legendTitle = legend.append('thead')
      .append('tr')
      .append('th')
      .text('2020 followers')
      .attr('colspan', 3)
      .style('position', 'relative')
      .style('left', '20px');

    const legendRows = legend.append('tbody')
      .selectAll('tr')
      .data(d3.entries(data[index].followers))
      .enter()
      .append('tr');

    legendRows.append('td')  
      .text(d => d.key)
      .attr('align', 'right');

    legendRows.append('td')
      .attr('align', 'center')
      .append('div')
      .style('width', '16px')
      .style('height', '16px')
      .style('background-color', d => pieColors(d.key))

    legendRows.append('td')
      .text(d => d.value)
      .attr('align', 'left');
  }

  drawDashboard(2020);
</script>

--solutions--

<script>
  const data = [ 
    { year: 2012, followers: { twitter: 2594, tumblr:  401, instagram:   83 }},
    { year: 2013, followers: { twitter: 3049, tumblr:  440, instagram:  192 }},
    { year: 2014, followers: { twitter: 3511, tumblr:  415, instagram:  511 }},
    { year: 2015, followers: { twitter: 3619, tumblr:  492, instagram: 1014 }},
    { year: 2016, followers: { twitter: 4046, tumblr:  543, instagram: 2066 }},
    { year: 2017, followers: { twitter: 3991, tumblr:  701, instagram: 3032 }},
    { year: 2018, followers: { twitter: 3512, tumblr: 1522, instagram: 4512 }},
    { year: 2019, followers: { twitter: 3274, tumblr: 1989, instagram: 4715 }},
    { year: 2020, followers: { twitter: 2845, tumblr: 2040, instagram: 4801 }}
  ];
</script>
<script>
  function drawDashboard(year) {
    d3.select('.dashboard').html('');
    const index = data.findIndex(d => d.year === year);

    const svgMargin = 70,
      svgWidth = 700,
      svgHeight = 500,
      twitterColor = '#7cd9d1',
      tumblrColor = '#f6dd71',
      instagramColor = '#fd9b98';

    const lineGraph = d3.select('.dashboard')
      .append('svg')
      .attr('width', svgWidth)
      .attr('height', svgHeight);

    const yScale = d3.scaleLinear()
      .domain([0, 5000])
      .range([svgHeight - svgMargin, svgMargin]);

    const xScale = d3.scaleLinear()
      .domain([2012, 2020])
      .range([svgMargin, svgWidth - svgMargin]);

    const yAxis = d3.axisLeft(yScale)
      .ticks(6, '~s');

    const xAxis = d3.axisBottom(xScale)
      .tickFormat(d3.format(''))
      .tickPadding(10);

    lineGraph.append('g')
      .call(yAxis)
      .attr('transform', `translate(${svgMargin}, 0)`)
      .style('font', '10px verdana');

    lineGraph.append('g')
      .call(xAxis)
      .attr('transform', `translate(0, ${svgHeight - svgMargin})`)
      .selectAll('text')
      .style('transform', 'translate(-12px, 0) rotate(-50deg)')
      .style('text-anchor', 'end')
      .style('cursor', 'pointer')
      .style('font', '10px verdana')


      .on('mouseover', d => drawDashboard(d));

    const twitterLine = d3.line()
      .x(d => xScale(d.year))
      .y(d => yScale(d.followers.twitter));

    lineGraph.append('path')
      .attr('d', twitterLine(data))
      .attr('stroke', twitterColor)
      .attr('stroke-width', 3)
      .attr('fill', 'transparent');

    const tumblrLine = d3.line()
      .x(d => xScale(d.year))
      .y(d => yScale(d.followers.tumblr));

    lineGraph.append('path')
      .attr('d', tumblrLine(data))
      .attr('stroke', tumblrColor)
      .attr('stroke-width', 3)
      .attr('fill', 'transparent');

    const instagramLine = d3.line()
      .x(d => xScale(d.year))
      .y(d => yScale(d.followers.instagram));

    lineGraph.append('path')
      .attr('d', instagramLine(data))
      .attr('stroke', instagramColor)
      .attr('stroke-width', 3)
      .attr('fill', 'transparent');
      
    lineGraph.selectAll('twitter-circles')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', d => xScale(d.year))
      .attr('cy', d => yScale(d.followers.twitter))
      .attr('r', 6)
      .attr('fill', 'white')
      .attr('stroke', twitterColor)
      .style('cursor', 'pointer')

    lineGraph.selectAll('tumblr-circles')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', d => xScale(d.year))
      .attr('cy', d => yScale(d.followers.tumblr))
      .attr('r', 6)
      .attr('fill', 'white')
      .attr('stroke', tumblrColor)
      .style('cursor', 'pointer')

    lineGraph.selectAll('instagram-circles')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', d => xScale(d.year))
      .attr('cy', d => yScale(d.followers.instagram))
      .attr('r', 6)
      .attr('fill', 'white')
      .attr('stroke', instagramColor)
      .style('cursor', 'pointer')

    const rightDashboard = d3.select('.dashboard')
      .append('div');

    const pieGraph = rightDashboard.append('svg')
      .attr('width', 200)
      .attr('height', 200)
      .style('position', 'relative')
      .style('left', '20px');

    const pieArc = d3.arc()
      .outerRadius(100)
      .innerRadius(0);

    const pieColors = d3.scaleOrdinal()  
      .domain(data[index].followers)
      .range([twitterColor, tumblrColor, instagramColor]);

    const pie = d3.pie()
      .value(d => d.value);
      
    const pieGraphData = pieGraph.selectAll('pieSlices')
      .data(pie(d3.entries(data[index].followers)))
      .enter()
      .append('g')
      .attr('transform', 'translate(100, 100)');

    pieGraphData.append('path')
      .attr('d', pieArc)
      .attr('fill', d => pieColors(d.data.key))
      .attr('stroke', 'white')
      .attr('stroke-width', 2);

    pieGraphData.append('text')
      .text(d => {
        const values = d3.values(data[index].followers);
        const sum = d3.sum(values);
        const percent = d.data.value/sum;
        return `${ Math.round(percent*100) }%`;
      })
      .attr('transform', d => `translate(${pieArc.centroid(d)})`)
      .style('text-anchor', 'middle')
      .style('font', '10px verdana');

    const legend = rightDashboard.append('table')
      .attr('width', 200)
      .attr('height', 120)
      .style('font', '12px verdana')
      .style('position', 'relative')
      .style('top', '30px');

    const legendTitle = legend.append('thead')
      .append('tr')
      .append('th')
      .text('2020 followers')
      .attr('colspan', 3)
      .style('position', 'relative')
      .style('left', '20px');

    const legendRows = legend.append('tbody')
      .selectAll('tr')
      .data(d3.entries(data[index].followers))
      .enter()
      .append('tr');

    legendRows.append('td')  
      .text(d => d.key)
      .attr('align', 'right');

    legendRows.append('td')
      .attr('align', 'center')
      .append('div')
      .style('width', '16px')
      .style('height', '16px')
      .style('background-color', d => pieColors(d.key))

    legendRows.append('td')
      .text(d => d.value)
      .attr('align', 'left');
  }

  drawDashboard(2020);
</script>