javascriptapexcharts

How to modify existing marker shape (triangle to upside down triangle)?


Regarding Apexcharts: How can I create a new shape or modify the existing triangle shape to be upside down?

markers: {
    size: 0,
    colors: undefined,
    strokeColors: '#fff',
    strokeWidth: 2,
    strokeOpacity: 0.9,
    strokeDashArray: 0,
    fillOpacity: 1,
    discrete: [],
    shape: "triangle",
    offsetX: 0,
    offsetY: 0,
    onClick: undefined,
    onDblClick: undefined,
    showNullDataPoints: true,
    hover: {
      size: undefined,
      sizeOffset: 3
    }
}

Solution

  • Apexcharts, in its current version, isn't designed to allow user defined marker icons. However, a hacky solution can be found.

    The function Graphics#getMarkerPath is the one responsible for creating the svg paths for the markers. So, we can override that method to add our new icons.

    What that method should return is a SVG path; the method signature is getMarkerPath(x, y, type, size), where x and y are the coordinates of the marker, type is one of the existing types (circle, square, triangle, etc.) and size is its size. One can look at existing icons implemented in the source code for getMarkerPath to understand how to create new ones. The actual position of the point is x, y, while the figure should be drawn inside a square centered on x, y with sides of 2 * size, that is [x - size, x + size] X [y - size, y + size].

    In order to access the Graphics class that implements the getMarkerPath method, we need a chart instance. Therefore, we may call our function that installs the new shapes between the constructor and the call to render():

    .............
    const chart = new ApexCharts(document.querySelector("#chart"), option);
    installMyMarkerShapes(chart);
    chart.render();
    

    with the installMyMarkerShapes function having the following model (with two example new shapes, invTriangle and heart)

    function installMyMarkerShapes(chart){
        const GraphicsProto = chart.graphics.constructor.prototype;
        const original_getMarkerPath = GraphicsProto.getMarkerPath;
        GraphicsProto.getMarkerPath = function(x, y, type, size){
            if(type === "invTriangle"){
                return `M ${x} ${y+size} L ${x-size} ${y-size} L ${x+size} ${y-size} Z`;
            }
            else if (type === 'heart'){
                const sz1 = Math.ceil(1.2*size),
                    sz2 = Math.ceil(1.2*size-size/2.5),
                    sz3 = Math.round(size/2.5),
                    r = Math.ceil(size/5);
                return `M ${x} ${y+size} l ${-sz2} ${-sz1} a ${r} ${r} -20 1 1 ${sz2} ${-sz3} a ${r} ${r} 20 1 1 ${sz2} ${sz3} Z`;
            }
            else return original_getMarkerPath.call(this, x, y, type, size);
        }
    }
    

    Here's a full example using that approach:

    function installMyMarkerShapes(chart){
        const GraphicsProto = chart.graphics.constructor.prototype;
        const original_getMarkerPath = GraphicsProto.getMarkerPath;
        GraphicsProto.getMarkerPath = function(x, y, type, size){
            if(type === "invTriangle"){
                return `M ${x} ${y+size} L ${x-size} ${y-size} L ${x+size} ${y-size} Z`;
            }
            else if (type === 'heart'){
                const sz1 = Math.ceil(1.2*size),
                    sz2 = Math.ceil(1.2*size-size/2.5),
                    sz3 = Math.round(size/2.5),
                    r = Math.ceil(size/5);
                return `M ${x} ${y+size} l ${-sz2} ${-sz1} a ${r} ${r} -20 1 1 ${sz2} ${-sz3} a ${r} ${r} 20 1 1 ${sz2} ${sz3} Z`
            }
            else return original_getMarkerPath.call(this, x, y, type, size);
        }
    }
    
    const option = {
       series: [
           {
               name: 'Data 1',
               data: Array.from({length: 10}, ()=> Math.floor(Math.random()*30)),
           },
           {
               name: 'Data 2',
               data: Array.from({length: 10}, ()=> Math.floor(Math.random()*30)),
               color: '#ff2233'
           },
           {
               name: 'Data 3',
               data: Array.from({length: 10}, ()=> Math.floor(Math.random()*30)),
           },
       ],
       chart: {
           type: 'line',
           height: 400,
           background: '#335',
           foreColor: '#fff'
       },
        stroke:{
           width: 2
        },
       markers: {
           strokeColors: '#fff',
           strokeWidth: 1,
           strokeOpacity: 0.9,
           strokeDashArray: 0,
           fillOpacity: 1,
           size: 5,
           shape: ["invTriangle", "heart", "triangle"]
       },
       dataLabels: {
          enabled: false
       },
       plotOptions:{
          area: {
             fillTo: 'end'
          }
       }
    };
    const chart = new ApexCharts(document.querySelector("#chart"), option);
    installMyMarkerShapes(chart);
    chart.render();
    <div id="chart"></div>
    <script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>