Gracie williams
Gracie williams

Reputation: 1145

Drag the chart of Lightningchart programatically

I want to drag the charts around using Key board keys like up arrow to pan the chart up , down arrow to pan the chart down etc.

const lcjs = lightningChart({
        overrideInteractionMouseButtons: {
            chartXYPanMouseButton: 0,
            chartXYRectangleZoomFitMouseButton: 2,
        },
})

const chart = lcjs.ChartXY()

Above code is fine with mouse , but I tried something like below using javascript , where I try to move the chart based on my arrow keys in keyboard.

   function upArrowPress() {
       move = 2;
       lastpos_y1 = lastpos_y1 + move;
       lastpos_y2 = lastpos_y2 + move;
       for (var key in axis) {                                 
         axis[key].setScrollStrategy(undefined).setInterval(lastpos_y1[key],lastpos_y2[key])
       }
   }

Above code works but each axis is moving differently , I am not able to get the native effect where all axis pan together.How do I achieve that smooth pan with custom functions.Thank you.

Upvotes: 2

Views: 520

Answers (1)

Snekw
Snekw

Reputation: 2620

There is no guarantee that both X and Y axis intervals would be visually same size. This is why when you set the axes intervals to be same, it can still result in visually different size change.

You need to adjust the interval changes by the size of pixel on each series scale direction. To do that you first need to get the pixel size for each scale direction (X and Y) series.scale.x.getPixelSize() and series.scale.y.getPixelSize(). These two values are multipliers that you can then use to change the intervals to be visually same change.

const offsetX = 1 // offset should be -1,0,1 based on what keys are pressed
const origXInterval = series.axisX.getInterval();
const xIntervalSize = origXInterval.end - origXInterval.start
const pixelSizeX = series.scale.x.getPixelSize()
const newXIntervalStart = origXInterval.start + offsetX * pixelSizeX * 10
axisX.setInterval(newXIntervalStart, newXIntervalStart + xIntervalSize)

The key here is + offsetX * pixelSizeX * 10. This would offset the X interval to left (-1) or right (1), the value on interval that 10 pixels would represent.

To do this for all axes and series you need to loop through each series chart.getSeries().forEach(series=>{}) and offset all axes found for any series once. One axis might be used by multiple series so you need to keep track if you have already offsetted the axis, otherwise the panning will get out of sync. You can do that easily with JS Set. Check if the axis is in the set already processedAxisSet.has(series.axisX) and if it is not there then do the offsetting as previously done and add the axis to the set processedAxisSet.add(series.axisX).

See below for a working full example. It's a ready html file that you can copy and paste to you local file system and then open in browser to see it working. I did it this way as iframes cause some issues with attaching events to the window.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />

    <!-- Flexbox styling to have the chart and header fill the page.
        Chart will take as much space as possible. -->
    <style>
        html,
        body {
            height: 100%;
            margin: 0;
        }

        .box {
            display: flex;
            flex-flow: column;
            height: 100%;
        }

        .box .row.header {
            flex: 0 1 auto;
        }

        .box .row.content {
            flex: 1 1 auto;
        }
    </style>
</head>

<body class="box">
    <!-- Create div to render the chart into-->
    <div id="target" class="row content"></div>

    <!--IIFE assembly (lcjs.iife.js) is a standalone JS file, 
      which does not need any build tools, 
      such as NPM, to be installed-->
    <!--Script source must be defined in it's own script tag-->
    <script src="https://unpkg.com/@arction/[email protected]/dist/lcjs.iife.js"></script>

    <!--Actual chart related script tag-->
    <script>
        // Replace the contents of this script tag if you want to test code from our examples:
        // https://www.arction.com/lightningchart-js-interactive-examples/

        // Extract required parts from LightningChartJS.
        const {
            lightningChart
        } = lcjs //Note: @arction/lcjs is not needed here, when using IIFE assembly

        // Create a XY Chart.
        const chart = lightningChart().ChartXY({
            // Set the chart into a div with id, 'target'. 
            // Chart's size will automatically adjust to div's size. 
            container: 'target'
        })

        const axisX = chart.getDefaultAxisX();
        const axisY = chart.getDefaultAxisY();
        const axisX2 = chart.addAxisX({ opposite: true })
        const axisY2 = chart.addAxisY({ opposite: true })

        axisX.setScrollStrategy(undefined)
        axisY.setScrollStrategy(undefined)
        axisX2.setScrollStrategy(undefined)
        axisY2.setScrollStrategy(undefined)

        const lineSeries = chart.addLineSeries()
        lineSeries.addArrayY([1, 2, 1, 2, 3, 4, 1, 2], 1, 1)

        const pointSeries = chart.addPointSeries({
            xAxis: axisX2,
            yAxis: axisY2
        })
        pointSeries.addArrayY([2, 4, 1, 2, 7, 3, 4, 5, 1, 2], 1, 1)

        // Create a keymap to track key states
        const keymap = new Map();
        keymap.set('ArrowUp', 0);
        keymap.set('ArrowLeft', 0);
        keymap.set('ArrowRight', 0);
        keymap.set('ArrowDown', 0);

        // attach listeners to keydown and keyup events to keep track of key states
        // keydown is also used to trigger update to pan the chart based on keyboard input
        document.addEventListener('keydown', function (ev) {
            keymap.set(ev.code, 1);
            updateKeyboardPan();
        });

        document.addEventListener('keyup', function (ev) {
            keymap.set(ev.code, 0);
        });

        function updateKeyboardPan() {
            // update offsets based on keyboard state
            let offsetX = 0
            let offsetY = 0
            if (keymap.get('ArrowUp') === 1) {
                offsetY -= 1
            }
            if (keymap.get('ArrowDown') === 1) {
                offsetY += 1
            }
            if (keymap.get('ArrowLeft') === 1) {
                offsetX += 1
            }
            if (keymap.get('ArrowRight') === 1) {
                offsetX -= 1
            }

            // set for storing what axes have already been processed
            const processedAxisSet = new Set()

            chart.getSeries().forEach(series => {
                // offset based on pixels only if the axis hasn't been processed this loop
                if (!processedAxisSet.has(series.axisX)) {
                    // Get original state info
                    const origXInterval = series.axisX.getInterval();
                    const xIntervalSize = origXInterval.end - origXInterval.start
                    // get the pixel size for axis 
                    const pixelSizeX = series.scale.x.getPixelSize()
                    const newXIntervalStart = origXInterval.start + offsetX * pixelSizeX * 10
                    // pixel size info is used to scale the change to be visually same size for both X and Y axis
                    series.axisX.setInterval(newXIntervalStart, newXIntervalStart + xIntervalSize)
                    // add the axis to the processed axis set
                    processedAxisSet.add(series.axisX)
                }

                // Do same for Y axis as was done for X axis
                if (!processedAxisSet.has(series.axisY)) {
                    const origYInterval = series.axisY.getInterval();
                    const yIntervalSize = origYInterval.end - origYInterval.start
                    const pixelSizeY = series.scale.y.getPixelSize()
                    const newYIntervalStart = origYInterval.start + offsetY * pixelSizeY * 10
                    series.axisY.setInterval(newYIntervalStart, newYIntervalStart + yIntervalSize)
                    processedAxisSet.add(series.axisY)
                }
            })
        }
    </script>
</body>

</html>

Upvotes: 1

Related Questions