Overlapping end ticks on full circle panel gauges

Full circle panel gauges have overlapping end ticks.

objs = pn.FlexBox(
            pn.indicators.Gauge(
                name="Roll",
                value=0,
                start_angle=-90,
                end_angle=270,
                bounds=(-180, 180),
                format="{value} Degs",
                colors=[(0.25, "red"), (0.75, "green"), (1, "red")],
                custom_opts={
                    "splitNumber": 12,
                },
            ),
            pn.indicators.Gauge(
                name="Heading",
                value=0,
                start_angle=90,
                end_angle=-270,
                bounds=(0, 360),
                format="{value} Degs",
                # colors=[(0.2, "red"), (0.8, "green"), (1, "red")],
                custom_opts={
                    "splitNumber": 12,
                    "labelLayout": {"hideOverlap": True},
                },
            ),
        )

In the image below (which is a little hard to see), each gauge has duplicate numbers displayed at the last tick (-180,180 & 0,360).
image

Echarts example clock has a function to remove the final tick. But I’m not sure how to implement this formatter function with custom-opts.

axisLabel: {
        fontSize: 50,
        distance: 25,
        formatter: function (value) {
          if (value === 0) {
            return '';
          }
          return value + '';
        }

Thanks in advance

Just giving this a bump in case someone is able to help

Can you submit a GitHub issue?

1 Like

Seems like Simon has made a PR for it already!

1 Like

You might also create a custom JSComponent for echarts

import panel as pn
import param
from panel.custom import JSComponent

# Make sure Panel has the needed dependencies
pn.extension()

# Create a custom JSComponent for ECharts Gauges
class EChartsGauge(JSComponent):
    """Custom ECharts Gauge component using JSComponent."""
    
    # Parameters
    value = param.Number(default=0)
    name = param.String(default="Gauge")
    min_value = param.Number(default=0)
    max_value = param.Number(default=100)
    start_angle = param.Number(default=225)
    end_angle = param.Number(default=-45)
    format_string = param.String(default="{value}")
    split_number = param.Integer(default=10)
    colors = param.List(default=None)
    hide_overlapping_labels = param.Boolean(default=False)
    
    # External script dependencies
    _importmap = {
        "imports": {
            "echarts": "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.esm.min.js"
        }
    }
    
    # JavaScript implementation
    _esm = """
    import * as echarts from 'echarts';
    
    export function render({ model, el }) {
        // Create container for the chart
        const container = document.createElement('div');
        container.style.width = '100%';
        container.style.height = '100%';
        el.appendChild(container);
        
        // Initialize ECharts instance
        const chart = echarts.init(container);
        
        function updateChart() {
            // Base configuration
            const option = {
                series: [{
                    "name": "Gauge",
                    "type": "gauge",
                    "axisTick": {"show": true},
                    "axisLabel": {"show": true},
                    "title": {"fontWeight": "bold", "fontSize": 18},
                    "splitLine": {"show": true},
                    "radius": "100%",
                    "detail": {"formatter": model.format_string},
                    "min": model.min_value,
                    "max": model.max_value,
                    "startAngle": model.start_angle,
                    "endAngle": model.end_angle,
                    "splitNumber": 12,
                    "data": [{"value": model.value, "name": model.name}],
                    "axisLine": {"lineStyle": {"width": 10}},
                    "labelLayout": {"hideOverlap": true},
                    "axisLabel": {hideOverlap: true}
                }
            ]
            };
            
            // Add colors if provided
            if (model.colors && model.colors.length) {
                option.series[0].axisLine = {
                    lineStyle: {
                        width: 10,
                        color: model.colors
                    }
                };
            }
            
            option.series[0].axisLabel.formatter = function(value) {
                return value === model.min_value ? '' : model.format_string.replace('{value}', value);
            };
            
            // Apply options to chart
            chart.setOption(option);
        }
        
        // Initialize chart
        updateChart();
        
        // Handle value changes
        model.on('value', () => {
            chart.setOption({
                series: [{
                    data: [{
                        value: model.value
                    }]
                }]
            });
        });
        
        // Handle parameter changes that require full update
        model.on(['min_value', 'max_value', 'start_angle', 'end_angle', 'split_number', 'colors', 'format_string', 'hide_overlapping_labels'], updateChart);
        
        // Handle resize events
        model.on('resize', () => {
            chart.resize();
        });
        
        // Handle layout changes
        model.on('after_layout', () => {
            chart.resize();
        });
        
        return container;
    }
    """

# Create the Roll gauge
roll_gauge = EChartsGauge(
    name="",
    value=90,
    min_value=-180,
    max_value=180,
    start_angle=-90,
    end_angle=270,
    format_string="{value}°",
    split_number=12,
    colors=[(0.25, "red"), (0.75, "green"), (1, "red")],
    height=350, width=350, margin=25
)

# Create the Heading gauge
heading_gauge = EChartsGauge(
    name="Heading",
    value=0,
    min_value=0,
    max_value=360,
    start_angle=90,
    end_angle=-270,
    format_string="{value}°",
    split_number=12,
    hide_overlapping_labels=True,
    height=350, width=350, margin=25
)

objs = pn.FlexBox(roll_gauge, heading_gauge, sizing_mode="stretch_width")
objs.servable()