In my current project I had to create a gauge chart with plotly. There is an example on the plotly website for Python, Javascript and R.

If you take just a quick look on this example, you might think it is quite easy to implement and use it. But if you take a closer look how they did the gauge chart you will facepalm yourself. So lets take a closer look on this example.

Plotlys gauge chart example

Gauge chart with plotly

You may wonder why half of the image is white and if it is an error. But as you will see later, this is caused by the chart they build.

In their example they use a standard pie chart:

{
  values: [
    50/6,
    50/6,
    50/6,
    50/6,
    50/6,
    50/6,
    50
  ],
  rotation: 90,
  text: [
    "TOO FAST!",
    "Pretty Fast",
    "Fast",
    "Average",
    "Slow",
    "Super Slow",
    ""
  ],
  textinfo: "text",
  textposition: "inside",
  marker: {
    colors: [
      "rgba(14, 127, 0, .5)",
      "rgba(110, 154, 22, .5)",
      "rgba(170, 202, 42, .5)",
      "rgba(202, 209, 95, .5)",
      "rgba(210, 206, 145, .5)",
      "rgba(232, 226, 202, .5)",
      "rgba(255, 255, 255, 0)"
    ]
  },
  labels: [
    "151-180",
    "121-150",
    "91-120",
    "61-90",
    "31-60",
    "0-30",
    ""
  ],
  hoverinfo: "label",
  hole: .5,
  type: "pie",
  showlegend: false
}

The trick they did is to put a hole into the pie with an radius of 0.5, now they have a donut chart. What they did in addition is kind of tricky, they put N + 1 values to the pie/donut Chart. N is the number of gauge chart elements you want to have. You also have to define N + 1 colors and N + 1 labels for the chart. So as I just needed 4 gauge chart elements, i had to write 5 elements into value array:

{
  values: [
    1,
    1,
    1,
    1,
    4
  ]
  ...
}

You now have 5 Elements, the first 4 are with a size of one and the last has a size of 4 which makes it to half of the pie/donut. I also removed the labels section and modified the colors and text sections to fit my requirements. I modified the last color that you can see how the chart is drawn.

Gauge chart with 4 elements

Fixing the height

The problem you now have is to remove the half of the bottom block you got. For that you have to do some css magic. Realy important is to choose the same height as width! Otherwise the calculated pointer will not point on the correct value, because the x and y position of the pointers end is calculated by using Math.sin and Math.cos. I will pick up this point later, but for now we will focus on removing the bottom part:

First we want to hide the lower part of the chart, for that we have to restrict the height of the outer container to the half of the chart size.

.chart {
    max-height: calc(200px + 20px) !important;
    overflow:hidden;
}

I choose to take 20 px more because it gives a nice effect if you have a small grey bar on outer edges of the gauge chart elements. Also the plot isn’t correctly centralized so you have the middle of the donut chart at width/2 +10px.
The red dot of the pointer also needs to get into our visibile chart, there are 2 options:

Option 1: make the dot smaller

{
    type: 'scatter',
    x:[0],
    y:[0],
    marker: {
      size: 14,
      color:'850000'
    },
    showlegend: false
}

Option 2: make the visible area bigger

.chart {
    max-height: calc(200px + 40px) !important;
    overflow:hidden;
}

The second option will make more from the lower part of the chart visible and isn’t recommended. I would prefer to choose the first version. So this is how the final result should look like.

Problem with the pointer

The pointer is represented by an svg-path and small scatter in the middle of the chart. The svg-path is the reason of the problem, the total path is set up by 2 constant points and one calculated point for the top of the pointer.

‚M -0.0 -0.025 L 0.0 0.025 L X Y Z‘

degrees = 180 - level
The calculation of X coordinate from the pointer is:
x = radius * Math.cos(degrees * pi / 180)
The calculation of Y coordinate from the pointer is:
y = radius * Math.sin(degrees * pi / 180)

You can see the 3 points in the following image, the A marks the first point of the svg-path M -.0 -0.025, B marks the second point of the path L .0 0.025 and the C marks the variable point wich is calculated L X Y Z.

If you come to the point that your C is on top of the other 2 points the triangle wont be drawn. This will be the case if your pointer should show exactly 50%.

There is a small workaround to fix this, if you just switch the A and B points from top and bottom to left and right ordered. Now you have no problem at 50% but you will get one at 0% and 100%.
To fix this, we will put an if statement onto the svg-path structure:

path = (degrees < 45 || degrees > 135) ? 'M -0.0 -0.025 L 0.0 0.025 L `X` `Y` Z' : 'M -0.025 -0.0 L 0.025 0.0 L `X` `Y` Z'

Now you will switch from top and bottom to left and right order if you get between 25% and 75%. You will recoginze small thickness changes when you get your curser near the boundary.

// Enter a speed between 0 and 180
var level = 90;

// Trig to calc meter point
var degrees = 180 - level,
     radius = .5;
var radians = degrees * Math.PI / 180;
var x = radius * Math.cos(radians);
var y = radius * Math.sin(radians);
var path1 = (degrees < 45 || degrees > 135) ? 'M -0.0 -0.025 L 0.0 0.025 L ' : 'M -0.025 -0.0 L 0.025 0.0 L ';
// Path: may have to change to create a better triangle
var mainPath = path1,
     pathX = String(x),
     space = ' ',
     pathY = String(y),
     pathEnd = ' Z';
var path = mainPath.concat(pathX,space,pathY,pathEnd);

var data = [{ type: 'scatter',
   x: [0], y:[0],
    marker: {size: 14, color:'850000'},
    showlegend: false,
    name: 'speed',
    text: level,
    hoverinfo: 'text+name'},
  { values: [1,1,1,1,4],
  rotation: 90,
  text: ['Excellent', 'Average', 'Warning', 'Poor', ''],
  textinfo: 'text',
  textposition:'inside',
  marker: {colors:['rgba(14, 127, 0, .5)', 'rgba(110, 154, 22, .5)',
                         'rgba(249, 168, 37, .5)', 'rgba(183,28,28, .5)',
                         'rgba(0, 0, 0, 0.5)']},
  hoverinfo: 'label',
  hole: .5,
  type: 'pie',
  showlegend: false
}];

var layout = {
  shapes:[{
      type: 'path',
      path: path,
      fillcolor: '850000',
      line: {
        color: '850000'
      }
    }],
  height: 400,
  width: 400,
  xaxis: {zeroline:false, showticklabels:false,
             showgrid: false, range: [-1, 1]},
  yaxis: {zeroline:false, showticklabels:false,
             showgrid: false, range: [-1, 1]}
};

Plotly.newPlot('myDiv', data, layout);
.chart {
    max-height: calc(200px + 20px) !important;
    overflow:hidden;
}
<head>
  <!-- Plotly.js -->
  <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
  <div id="myDiv" class="chart"><!-- Plotly chart will be drawn inside this DIV --></div>
</body>

Correct triangle

To get a correct triangle you have to move your A and B points with your C point. As your C is calculated by a specific formula we have to do the same for A and B:
degrees = 180 - level

A.X = 0.025 * Math.cos((degrees -90) * pi / 180)

A.Y = 0.025 * Math.sin((degrees -90) * pi / 180)

B.X = -0.025 * Math.cos((degrees -90) * pi / 180)

B.Y = -0.025 * Math.sin((degrees -90) * pi / 180)

var degrees = 180 - level,
     radius = .5;
var radians = degrees * Math.PI / 180;
var aX = 0.025 * Math.cos((degrees-90) * Math.PI / 180);
var aY = 0.025 * Math.sin((degrees-90) * Math.PI / 180);
var bX = -0.025 * Math.cos((degrees-90) * Math.PI / 180);
var bY = -0.025 * Math.sin((degrees-90) * Math.PI / 180);
var cX = radius * Math.cos(radians);
var cY = radius * Math.sin(radians);

var path = 'M ' + aX + ' ' + aY +
             ' L ' + bX + ' ' + bY +
             ' L ' + cX + ' ' + cY +
             ' Z';

Summary

You have seen that it is possible to create Gauge charts with plotly. But it is not as simple as it looks like on the first glance. It is kind of tricky and a bit hacky. Probably first ask yourself if you really need a Gauge chart. If so, you should better consider using Google Charts, which has native support for gauge charts. If for some reason you are bound to plotly (like I am), you should now know how to do it with plotly. Another mentionable framework, if you work with angular, is ngx-Charts.

You can find the full code of the example source code on github.