project-nv / night-vision

Highly customizable charting library, created for professional traders
https://nightvision.dev
MIT License
242 stars 57 forks source link

Overlay Disappears Depending on Visible Range of Candles #96

Closed 0xTechnician closed 1 year ago

0xTechnician commented 1 year ago

Describe the bug

First of all, thanks a lot for your work and this amazing library.

Issue: I am using the NightVision charting library. While I have been successful in plotting the candles and my custom overlay on top of the candles in my pane, I am facing an issue where some of my overlays disappear based on the visible range of candles.

Description: The data of the overlay "Custom" plotted on the chart is fetched from an API upon the load of the chart. However, the overlay "Custom" seems to be inconsistent in its behavior as it disappears based on the visible range of candles.

Reproduction:

  1. Load the chart with a specific range of candles.
  2. Notice that the custom overlay is present.
  3. Adjust the visible range of candles.
  4. Some of the custom overlays disappear.

Code Snippets:

async function fetchOverlayData(symbol, interval) {
    // using Axios and returning data array
}

async function fetchData(symbol = "BTCUSDT", interval = "5m") {
    const candleData = await fetchCandleData(symbol, interval);
    const overlayData= await fetchOverlayData(symbol, interval);
    return {
        panes: [{
            overlays: [
                {
                    name: `${symbol}`,
                    type: "Candles",
                    main: true,
                    data: candleData,
                    settings: {},
                    props: {}
                },
                {
                    name: "Custom",
                    type: "Custom",
                    data: overlayData,
                    props: {}
                }
            ],
            settings: {},
        }]
    };
}

function updateChartData() {
    const selectedSymbol = document.querySelector("#ticker-select").value;
    const selectedTimeframe = document.querySelector("#timeframe-select").value;

    if (!selectedSymbol || !selectedTimeframe) {
        console.error('Selected symbol or timeframe is null or undefined.');
        return;
    }

    fetchData(selectedSymbol, selectedTimeframe)
    .then(data => {
        if (!data || !data.panes[0]) {
            console.error('Fetched data is null, undefined, or incorrectly structured.');
            return;
        }

        // Adjust the Overlay y coord before updating the chart
        const candleData = data.panes[0].overlays.find(overlay => overlay.type === "Candles").data;
        const overlayData= data.panes[0].overlays.find(overlay => overlay.type === "Custom").data;

        const adjustedOverlayData = adjustOverlayCrossPrices(overlayData, candleData);
        data.panes[0].overlays.find(overlay => overlay.type === "Custom").data = adjustedOverlayData;

        updateChart(data);
    })
    .catch(err => {
        console.error('Error fetching and updating data:', err);
    });
}

function updateChart(chartData) {
    console.log(chartData)
    if (window.chart) {
        window.chart.data = chartData;
        chart.update('full', {resetRange: true})
    } else {
        console.log(chartData)
        window.chart = new NightVision("chart-container", {
            data: chartData,
            scripts: [Custom],
            width: 1084,
            height: 720,
            autoResize: true,
            colors: {
                back: "#111113",
                grid: "#2e2f3055",
                candleDw: "#0c5b3bff",
                candleUp: "#41a35bff",
                volDw: "#29595555",
                volUp: "#5ba38d3f",
                wickDw: "#0c5b3b88",
                wickUp: "#41a35088"
            }
        });
    }
}

async function initialize() {
    const tickers = await fetchTickers();
    const selectElem = document.querySelector("#ticker-select");
    selectElem.innerHTML = tickers.map(ticker => `<option value="${ticker}">${ticker}</option>`).join('');

    selectElem.addEventListener("change", updateChartData);
    document.querySelector("#timeframe-select").addEventListener("change", updateChartData);

    updateChartData();
}

Custom.navy


// Navy ~ 0.1-lite
// ^^^ First comment should provide a NavyJS version

// Meta tag
[OVERLAY name=Custom, ctx=Canvas, author=ChartMaster, version=1.0.0]

// Define new props
// (the same as in 'settings.props' of Overlay object)
prop('radius', {  type: 'number', def: 3 })

// Any variables/constants
const _3Y = 60 * 60 * 24 * 365 * 3 * 1000

// Draw function (called on each update)
// Library provides a lot of useful variables to make
// overlays ($core in the main collection)

draw(ctx) {
    ctx.strokeStyle = $props.back
    ctx.lineWidth = 1
    const layout = $core.layout
    const data = $core.data // Full dataset
    const view = $core.view // Visible view
    const radius = $props.radius
    for (var i = view.i1, n = view.i2; i <= n; i++) {
        ctx.beginPath()
        let p = data[i]
        // Mapping function used to transform values into
        // coordinates
        let x = layout.time2x(p[0])
        let y = layout.value2y(p[1]['$'])
        ctx.fillStyle = gradient(p[1]['text'])

        ctx.arc(x, y, radius, 0, Math.PI * 2, false)
        ctx.fill()
    }
}

// Make a gradient depending on the y-value
gradient(val) {
    let lo = 10000
    let hi = 10000000
    let pos = (val - lo) / (hi - lo)
    let h = (1.0 - pos) * 240
    return "hsl(" + h + ", 90%, 50%)"
}

Typical overlayData is a list of items like this :

    1697932800000,
    {
        "sel": false,
        "$": 29565.9,
        "text": 10552652.28,
        "base": "BTC",
    }

Example : On load , before changing visible range before_changing_visible_range

After sliding a little bit the data to the left after_sliding_right

Reproduction

none

Steps to reproduce

No response

Javascript Framework

svelte

Logs

No response

Validations

C451 commented 1 year ago

Thanks, need to look into. Please, provide a codesandbox.

0xTechnician commented 1 year ago

Thanks, need to look into. Please, provide a codesandbox.

https://codesandbox.io/s/playing-around-7-forked-t4dy5v?file=/main.js

Here is a replication, you can play with timeframe / symbols to reproduce the bug described. It may be related to a lack of understanding of the library from me, I had exactly the same issue using TradingVueJs.

Thanks again.

C451 commented 1 year ago

Oh, this is classics :) Your data timestamps should be sorted. You also have a lot of duplicate ones. This loop:

for (var i = view.i1, n = view.i2; i <= n; i++) {
  //...
}

works with indices, which of course causes the mess. Need to add the requirement to the docs.

0xTechnician commented 1 year ago

Oh, this is classics :) Your data timestamps should be sorted. You also have a lot of duplicate ones. This loop:

for (var i = view.i1, n = view.i2; i <= n; i++) {
  //...
}

works with indices, which of course causes the mess. Need to add the requirement to the docs.

Oh, thanks a lot , it works way better now! I don't think there is any duplicate? If ever there was duplicates , why would it be a problem?

C451 commented 1 year ago

That's what I noticed. No, it's just a drag on the performance.