Search code examples
htmljquerycsschart.js

Rotating labels on lower half of pie chart


I am using Chart.js to generate a pie chart.

I want to rotate the labels on the lower half of the pie chart.

Here is my current code to generate the pie chart:

$(document).ready(
  function() {
    var data = {
      datasets: [{
        data: [125, 125, 125, 125, 125, 125, 125, 125],
        backgroundColor: ["#FE4D42","#FFD405","#58E152","#00D59F","#8B7DFF","#E85FED","#F71663","#4F94FF"]
      }],
      labels: [
            "Arts and Creativity",
            "Careers and Employment",
            "Communities and Social",
            "Culture and Heritage",
            "Learning and Skills",
            "Personal Finance",
            "Self-Environment",
            "Wellbeing"
      ]
    };
      
    var labels_url = {
      "Arts and Creativity" : "arts-and-creativity",
      "Careers and Employment" : "careers-and-employment",
      "Communities and Social" : "communities-and-social",
      "Culture and Heritage" : "culture-and-heritage",
      "Wellbeing" : "health",
      "Learning and Skills" : "learning-and-skills",
      "Personal Finance" : "personal-finance",
      "Self-Environment" : "self-environment"
    };
    
    var canvas = document.getElementById("myChart");
    var ctx = canvas.getContext("2d");
    var myNewChart = new Chart(ctx,{
      type: 'pie',
      data: data,
      options: {
        legend: {
            display: false, 
        },
        plugins: {
          labels: {
            render: 'label',
            arc: true,
            fontColor:["#FF7805","#0854A3","#14A400","#048A7D","#502788","#F200F2","#FF0068","#000962"],
            fontSize: 18,
            position: 'outside'
          }
        }
      }
    });

    canvas.onclick = function(evt) {
      var activePoints = myNewChart.getElementsAtEvent(evt);
      if (activePoints[0]) {
        var chartData = activePoints[0]['_chart'].config.data;
        var idx = activePoints[0]['_index'];

        var label = chartData.labels[idx];
        var value = chartData.datasets[0].data[idx];

        var url = "https://www.maximiseme.co.uk/" + labels_url[label];
        //alert(url);
        console.log(url);
        window.location.href = url;
      }
    };
  }
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
<script src="https://cdn.jsdelivr.net/gh/emn178/chartjs-plugin-labels/src/chartjs-plugin-labels.js"></script>

<canvas id="myChart"></canvas>

I want to rotate the following labels to 180 degrees for better readability: Communities and Social, Culture and Heritage, Learning and Skills, Personal Finance.


Solution

  • A good and easy answer is that what you want to achieve is not possible with the libraries you are using. The labels are drawn by the chartjs-plugin-labels, which doesn't have a way to change the sense in which the text is rendered, it always renders the labels in a clockwise sense. It is also not under active development, the latest version is from 6 years ago.

    But since the post contains a good minimal reproducible example, I thought to write a hacky solution through a plugin that patches the existing code in the chartjs-plugin-labels. Alternatively, one could consider forking the plugin repository and adding a similar piece of code in a more straightforward manner.

    This patch adds a new option to plugins.labels named reverse. If missing, reverse is false for all elements. It is similar to fontColor, indexable and scriptable. If an arc element has its reverse: true option, its label will be drawn in a counter-clockwise sense, otherwise it is drawn as usual, in a clockwise sense.

    There's no need for further explanations, one can understand the details from the code itself.

    var pluginLabelsExtension = {
        afterDatasetsUpdate(chart){
            if(chart._labels && chart._labels.length > 0){
                this._installLabelPatch(chart._labels[0].constructor);
            }
        },
    
        _installLabelPatch(Label){
            if(Label.prototype._patched){
                return;
            }
            Label.prototype._patched = true;
            const _getFontColor = Label.prototype.getFontColor,
                _renderArcLabel = Label.prototype.renderArcLabel;
    
            // use getFontColor to also get the `reverse` option for this element; save in element._view
            Label.prototype.getFontColor = function(dataset, element, index){
                var fontColor = _getFontColor.call(this, dataset, element, index);
                var reverse = false,
                    optionReverse = this.options.reverse;
                if(typeof optionReverse === typeof true){
                    reverse = optionReverse;
                }
                else if(typeof optionReverse === 'function'){
                    reverse = optionReverse({
                        label: this.chart.config.data.labels[index],
                        value: dataset.data[index],
                        percentage: this.getPercentage(dataset, element, index),
                        backgroundColor: dataset.backgroundColor[index],
                        dataset: dataset,
                        index: index
                    });
                }
                else if(Array.isArray(optionReverse)){
                    reverse = optionReverse[index];
                }
                element._view.reverse = reverse;
                return fontColor;
            }
    
            Label.prototype.renderArcLabel = function(label, renderInfo){
                if(renderInfo.view.reverse && typeof label === 'string'){
                    var ctx = this.ctx, radius = renderInfo.radius, view = renderInfo.view;
                    var deltaToCenterFromEnd = renderInfo.startAngle - view.startAngle - Math.PI / 2;
                    ctx.save();
                    ctx.translate(view.x, view.y);
                    ctx.rotate(renderInfo.endAngle - deltaToCenterFromEnd);
                    ctx.textBaseline = 'middle';
                    ctx.textAlign = 'left';
                    var lines = label.split('\n'), max = 0, widths = [], offset = 0;
                    if(this.options.position === 'border'){
                        offset = (lines.length - 1) * this.options.fontSize / 2;
                    }
                    for(var j = 0; j < lines.length; ++j){
                        var mertrics = ctx.measureText(lines[j]);
                        if(mertrics.width > max){
                            max = mertrics.width;
                        }
                        widths.push(mertrics.width);
                    }
                    for(var j = 0; j < lines.length; ++j){
                        var line = lines[j];
                        var y = (lines.length - 1 - j) * -this.options.fontSize + offset;
                        ctx.save();
                        var padding = (max - widths[j]) / 2;
                        ctx.rotate(padding / radius);////
                        for(var i = 0; i < line.length; i++){
                            var char = line.charAt(i);
                            mertrics = ctx.measureText(char);
                            ctx.save();
                            ctx.translate(0, -1 * radius);
                            ctx.rotate(Math.PI);
                            ctx.fillText(char, 0, y);
                            ctx.restore();
                            ctx.rotate(-mertrics.width / radius);
                        }
                        ctx.restore();
                    }
                    ctx.restore();
                }
                else{
                    _renderArcLabel.call(this, label, renderInfo)
                }
            }
        }
    }
    
    $(document).ready(
        function() {
            var data = {
                datasets: [{
                    data: [125, 125, 125, 125, 125, 125, 125, 125],
                    backgroundColor: ["#FE4D42","#FFD405","#58E152","#00D59F","#8B7DFF","#E85FED","#F71663","#4F94FF"]
                }],
                labels: [
                    "Arts and Creativity",
                    "Careers and Employment",
                    "Communities and Social",
                    "Culture and Heritage",
                    "Learning and Skills",
                    "Personal Finance",
                    "Self-Environment",
                    "Wellbeing"
                ]//.map(s => s.split(/[ \-]/)[0])
            };
    
            var labels_url = {
                "Arts and Creativity" : "arts-and-creativity",
                "Careers and Employment" : "careers-and-employment",
                "Communities and Social" : "communities-and-social",
                "Culture and Heritage" : "culture-and-heritage",
                "Wellbeing" : "health",
                "Learning and Skills" : "learning-and-skills",
                "Personal Finance" : "personal-finance",
                "Self-Environment" : "self-environment"
            };
    
            var canvas = document.getElementById("myChart");
            var ctx = canvas.getContext("2d");
            var myNewChart = new Chart(ctx,{
                type: 'pie',
                data: data,
                options: {
                    legend: {
                        display: false,
                    },
                    //animation: false,
                    plugins: {
                        labels: {
                            render: 'label',
                            arc: true,
                            fontColor:["#FF7805","#0854A3","#14A400","#048A7D","#502788","#F200F2","#FF0068","#000962"],
                            reverse: [false, false, true, true, true, true, false, false],
                            fontSize: 18,
                            position: 'outside'
                        }
                    }
                },
                plugins:[
                    pluginLabelsExtension
                ]
            });
    
            canvas.onclick = function(evt) {
                var activePoints = myNewChart.getElementsAtEvent(evt);
                if (activePoints[0]) {
                    var chartData = activePoints[0]['_chart'].config.data;
                    var idx = activePoints[0]['_index'];
    
                    var label = chartData.labels[idx];
                    var value = chartData.datasets[0].data[idx];
    
                    var url = "https://www.maximiseme.co.uk/" + labels_url[label];
                    //alert(url);
                    console.log(url);
                    window.location.href = url;
                }
            };
        }
    );
    <canvas id="myChart" style="width: 1000px; height: 1000px"></canvas>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/emn178/chartjs-plugin-labels/src/chartjs-plugin-labels.js"></script>