Search code examples
javascriptchart.js

Radar Chart Color Transition and Border Smoothing Issue


"I am trying to make smooth color transitions in graphics using Radar Chart. At the same time, I want the lines to be smooth, but I cannot get the results I want. The color transitions are smooth, but the lines do not fit well and the corners look hard. You can find the code I used below. Does anyone have a helpful solution suggestion ?enter image description here

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Radar Chart with Smooth Color Transitions</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      background-color: #252c37;
      margin: 0;
      font-family: Arial, sans-serif;
    }

    .chart-container {
      position: relative;
      width: 400px;
      height: 400px;
    }

    .center-text {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      text-align: center;
    }

    .main-number {
      font-size: 3em;
      color: #4CAF50;
    }

    .sub-text {
      font-size: 1.2em;
      color: #999;
    }
  </style>
</head>
<body>
  
  <div class="chart-container">
    <canvas id="radarChart"></canvas>
    <div class="center-text">
      <span class="main-number">87</span>
      <br />
      <span class="sub-text">Ad Liked</span>
    </div>
  </div>

  <script>
    // Özel plugin ile segmentleri renklendirme ve geçiş sağlama
    const multiColorPlugin = {
      id: 'multiColorPlugin',
      afterDatasetDraw(chart) {
        const { ctx } = chart;
        const meta = chart.getDatasetMeta(0);
        const points = meta.data;
        const tension = 0.3; // Daha yumuşak geçişler için tension

        const borderColors = [
          'rgba(255, 99, 132, 1)', // Pembe
          'rgba(54, 162, 235, 1)', // Mavi
          'rgba(255, 206, 86, 1)', // Sarı
          'rgba(75, 192, 192, 1)', // Turkuaz
          'rgba(153, 102, 255, 1)', // Mor
          'rgba(255, 159, 64, 1)'   // Turuncu
        ];

        ctx.save();
        for (let i = 0; i < points.length; i++) {
          const currentPoint = points[i];
          const nextPoint = points[(i + 1) % points.length];
          const prevPoint = points[(i - 1 + points.length) % points.length];

          // Kontrol noktaları hesaplanarak daha yumuşak geçiş sağlanıyor
          const cp1x = currentPoint.x + tension * (nextPoint.x - prevPoint.x);
          const cp1y = currentPoint.y + tension * (nextPoint.y - prevPoint.y);

          const gradient = ctx.createLinearGradient(currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y);
          gradient.addColorStop(0, borderColors[i]);
          gradient.addColorStop(1, borderColors[(i + 1) % points.length]);

          ctx.beginPath();
          ctx.moveTo(currentPoint.x, currentPoint.y);
          ctx.bezierCurveTo(cp1x, cp1y, nextPoint.x, nextPoint.y, nextPoint.x, nextPoint.y); // Daha yumuşak geçiş için bezierCurve
          ctx.strokeStyle = gradient;
          ctx.lineWidth = 3;
          ctx.stroke();
          ctx.closePath();
        }
        ctx.restore();
      }
    };

    // Radar grafiği verileri ve ayarları
    const data = {
      labels: ['Creativity', 'Originality', 'Emotion', 'Believable', 'Uniqueness', 'Different'],
      datasets: [{
        label: 'Ad Liked',
        data: [47, 91, 42, 88, 63, 83],
        fill: true,
        backgroundColor: 'rgba(0, 0, 0, 0.5)', // İç kısım siyah ve şeffaf
        pointBackgroundColor: 'rgba(0, 0, 0, 0)', // Noktaları gizle
        pointBorderColor: 'rgba(0, 0, 0, 0)', // Nokta kenarlarını gizle
        tension: 0.5 // Çizgilerin yumuşaklığı için tension
      }]
    };

    const config = {
      type: 'radar',
      data: data,
      options: {
        elements: {
          line: {
            borderWidth: 0 // Çizgi rengi plugin ile belirlenecek
          }
        },
        scales: {
          r: {
            angleLines: {
              display: false
            },
            suggestedMin: 0,
            suggestedMax: 100,
            ticks: {
              display: false // Etrafındaki sayıları gizler
            }
          }
        }
      },
      plugins: [multiColorPlugin] // Özel plugin kullanımı
    };

    // Radar grafiğini oluştur
    const radarChart = new Chart(
      document.getElementById('radarChart'),
      config
    );
  </script>

</body>
</html> Even though I made the transitions 0.5 to 0.5, it doesn't work.

Solution

  • A simple solution is to use the already available bezier parameters, that were computed for a periodic curve, which is smooth at all corners, using the chart.js source code that would have been used for the default hidden (borderWidth: 0) curve:

    ctx.bezierCurveTo(currentPoint.cp2x, currentPoint.cp2y, nextPoint.cp1x, nextPoint.cp1y, nextPoint.x, nextPoint.y);
    

    Original code with this modification, in a snippet:

    const multiColorPlugin = {
       id: 'multiColorPlugin',
       afterDatasetDraw(chart) {
          const { ctx } = chart;
          const meta = chart.getDatasetMeta(0);
          const points = meta.data;
          const tension = 0.3; // set tension through regular options
    
          const borderColors = [
             'rgba(255, 99, 132, 1)',
             'rgba(54, 162, 235, 1)',
             'rgba(255, 206, 86, 1)',
             'rgba(75, 192, 192, 1)',
             'rgba(153, 102, 255, 1)',
             'rgba(255, 159, 64, 1)'
          ];
    
          ctx.save();
          for (let i = 0; i < points.length; i++) {
             const currentPoint = points[i];
             const nextPoint = points[(i + 1) % points.length];
    
             const gradient = ctx.createLinearGradient(currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y);
             gradient.addColorStop(0, borderColors[i]);
             gradient.addColorStop(1, borderColors[(i + 1) % points.length]);
    
             ctx.beginPath();
             ctx.moveTo(currentPoint.x, currentPoint.y);
             ctx.bezierCurveTo(currentPoint.cp2x, currentPoint.cp2y, nextPoint.cp1x, nextPoint.cp1y, nextPoint.x, nextPoint.y);
             ctx.strokeStyle = gradient;
             ctx.lineWidth = 3;
             ctx.stroke();
             ctx.closePath();
          }
          ctx.restore();
       }
    };
    
    const data = {
       labels: ['Creativity', 'Originality', 'Emotion', 'Believable', 'Uniqueness', 'Different'],
       datasets: [{
          label: 'Ad Liked',
          data: [47, 91, 42, 88, 63, 83],
          fill: true,
          backgroundColor: 'rgba(0, 0, 0, 0.5)',
          pointBackgroundColor: 'rgba(0, 0, 0, 0)',
          pointBorderColor: 'rgba(0, 0, 0, 0)',
          tension: 0.5
       }]
    };
    
    const config = {
       type: 'radar',
       data: data,
       options: {
          elements: {
             line: {
                borderWidth: 0
             }
          },
          scales: {
             r: {
                angleLines: {
                   display: false
                },
                suggestedMin: 0,
                suggestedMax: 100,
                ticks: {
                   display: false
                }
             }
          }
       },
       plugins: [multiColorPlugin]
    };
    
    new Chart(
       'radarChart',
       config
    );
    <div style="width: 250px">
        <canvas id="radarChart" style="background-color:rgb(64,64,78)">
        </canvas>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>