Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto fitting to the height of pane1 histograms #1709

Open
vishnuc opened this issue Oct 14, 2024 · 20 comments
Open

Auto fitting to the height of pane1 histograms #1709

vishnuc opened this issue Oct 14, 2024 · 20 comments

Comments

@vishnuc
Copy link

vishnuc commented Oct 14, 2024

hi , I am testing out v5-candidate with multpane , I added volume series to new pane via

   const volumeSeries = chart.addHistogramSeries({
            color: '#26a69a', // Default color (used for volume bars)
            priceFormat: {
                type: 'volume',
            },
        }, 1); // Add to second pane (pane index 1)

now if i want to scale this volumeseries , it is also scaling the pane 0 price series.

volumeSeries.priceScale().applyOptions({
    autoScale: false, // disables auto scaling based on visible content
    scaleMargins: {
        top: 0.9,
        bottom: 0,
    },
});


Screenshot 2024-10-14 at 1 56 53 PM Screenshot 2024-10-14 at 1 57 43 PM

Its expected to fit / scale only pane 1 as the series is in pane1

@safaritrader
Copy link

image

use an id for price scale :

    const volumeSeries = chart.addHistogramSeries({
    color: '#26a69a',
    priceFormat: {
        type: 'volume',
    },
    priceScaleId: 'rightVolume',
}, 1);
chart.priceScale('rightVolume').applyOptions({
    autoScale: false,
    scaleMargins: {
        top: 0.9,
        bottom: 0,
    },
});

@vishnuc
Copy link
Author

vishnuc commented Oct 15, 2024

If i set priceScaleId: 'rightVolume', , I am not able to scale by dragging its pricescale.. also when i hover the volume its not displaying price label on right side.

@safaritrader
Copy link

If i set priceScaleId: 'rightVolume', , I am not able to scale by dragging its pricescale.. also when i hover the volume its not displaying price label on right side.

As I understand it, if you want to have full control over the volume, the best approach is to build a plugin, or you can use a secondary overlay canvas or a second price scale at the left

@vishnuc
Copy link
Author

vishnuc commented Oct 15, 2024

sorry , I am not a professional to build plugin..Also i want both price and volume scale on the right, thats why using the pane.

so can you tell me about secondary overlay canvas ?

@safaritrader
Copy link

sorry , I am not a professional to build plugin..Also i want both price and volume scale on the right, thats why using the pane.

so can you tell me about secondary overlay canvas ?

as i am a fan of pure js i created a plugin for lightweight chart. you can see the concept and use it :
https://github.com/safaritrader/lightweight-chart-plugin

@vishnuc
Copy link
Author

vishnuc commented Oct 16, 2024

wow , thanks i was thinking to create volume profile for my project , but did u extend version 4 or 5 ..i need multi pane support.

@safaritrader
Copy link

wow , thanks i was thinking to create volume profile for my project , but did u extend version 4 or 5 ..i need multi pane support.

your welcome. i am expanding that plugin frequently. it's based on v 4.2.0

@vishnuc
Copy link
Author

vishnuc commented Oct 16, 2024

ok , then I want to build plugin for my v5 , can you guide me with any basic tutorials ? there is no documentation in TV how to create a plugin from scratch.

I am coding vanilla js for past 8 years.. since there was no use for constructor , canvas I did not learn , but I am willing to learn now .. can you tell me what should I learn ? because I am new to constructor , canvas etc ..

is there a skeleton or template that i must follow to create plugin from scratch and how it works.

@safaritrader
Copy link

ok , then I want to build plugin for my v5 , can you guide me with any basic tutorials ? there is no documentation in TV how to create a plugin from scratch.

I am coding vanilla js for past 8 years.. since there was no use for constructor , canvas I did not learn , but I am willing to learn now .. can you tell me what should I learn ? because I am new to constructor , canvas etc ..

is there a skeleton or template that i must follow to create plugin from scratch and how it works.

As i know the last version for lightweight charts is 4.2

@vishnuc
Copy link
Author

vishnuc commented Oct 16, 2024

ok there is another branch named 5 , you can use that to checkout multi pane feature - https://github.com/tradingview/lightweight-charts/tree/v5-candidate

btw please tell me what should I learn , or any tutorials from scratch to create one simple plugin

@safaritrader
Copy link

ok there is another branch named 5 , you can use that to checkout multi pane feature - https://github.com/tradingview/lightweight-charts/tree/v5-candidate

btw please tell me what should I learn , or any tutorials from scratch to create one simple plugin

Official Library suggest to do that :
https://github.com/tradingview/lightweight-charts/tree/master/packages/create-lwc-plugin
which i am working with pure js and its not fitting my requirements and im using overlay canvas which i described in my Lightwight charts plugin. its very simple when you read you will learn!. the main approach of mine is to use another canvas over the lwc canvas and do every thing you want

@vishnuc
Copy link
Author

vishnuc commented Oct 16, 2024 via email

@vishnuc
Copy link
Author

vishnuc commented Oct 19, 2024

Hey @safaritrader I saw plugin use fancy-canvas etc , how did u overcome all those things using vanilla js.

@safaritrader
Copy link

safaritrader commented Oct 19, 2024

Hey @safaritrader I saw plugin use fancy-canvas etc , how did u overcome all those things using vanilla js.

Hi Dear @vishnuc
Sometimes a simple solution is faster for achieving the desired outcome, plus I tested that plugin on 3 million data points for volume profile, and it worked perfectly. I try not to get too caught up in complexities and aim to reach my goal with the simplest approach. However, there are still some small bugs that I'll fix in the next update. Since I work with large datasets, I need to make sure the code can handle at least 10 million data points. One of the good features of this plugin is that it doesn’t require extensive expertise to modify or rewrite.

@vishnuc
Copy link
Author

vishnuc commented Oct 19, 2024

hi , but I compiled and run your example.. its not fluid when i scale price axis .. native plugins are way smoother...also when time axis is scaled I think you redraw after certain interval.

@safaritrader
Copy link

hi , but I compiled and run your example.. its not fluid when i scale price axis .. native plugins are way smoother...also when time axis is scaled I think you redraw after certain interval.

its depend on your data scale. for now its loop through all data but im replacing a simple algorithm to not loop through all data in next version and its depend on your browser also it take 5 sec to build a volume profile from 3m data (tested in firefox). other plugins are using lightweight chart built-in feature's which is great but not fit my requirements

@vishnuc
Copy link
Author

vishnuc commented Oct 19, 2024

hi , i succeeded creating primitive with pure js , its smooth . now working on series , will update you.

    <script >
 
        class AnchoredTextRenderer {
            constructor(options) {
                this._data = options;
            }

            draw(target) {
                target.useMediaCoordinateSpace((scope) => {
                    const ctx = scope.context;
                    ctx.font = this._data.font;
                    const textWidth = ctx.measureText(this._data.text).width;
                    const horzMargin = 20;
                    let x = horzMargin;
                    const width = scope.mediaSize.width;
                    const height = scope.mediaSize.height;

                    switch (this._data.horzAlign) {
                        case 'right':
                            x = width - horzMargin - textWidth;
                            break;
                        case 'middle':
                            x = width / 2 - textWidth / 2;
                            break;
                    }

                    const vertMargin = 10;
                    const lineHeight = this._data.lineHeight;
                    let y = vertMargin + lineHeight;

                    switch (this._data.vertAlign) {
                        case 'middle':
                            y = height / 2 + lineHeight / 2;
                            break;
                        case 'bottom':
                            y = height - vertMargin;
                            break;
                    }

                    ctx.fillStyle = this._data.color;
                    ctx.fillText(this._data.text, x, y);
                });
            }
        }

        class AnchoredTextPaneView {
            constructor(source) {
                this._source = source;
            }

            update() {}

            renderer() {
                return new AnchoredTextRenderer(this._source._data);
            }
        }

        class AnchoredText {
            constructor(options) {
                this._data = options;
                this._paneViews = [new AnchoredTextPaneView(this)];
            }

            updateAllViews() {
                this._paneViews.forEach(pw => pw.update());
            }

            paneViews() {
                return this._paneViews;
            }

            attached({ requestUpdate }) {
                this.requestUpdate = requestUpdate;
            }

            detached() {
                this.requestUpdate = undefined;
            }

            applyOptions(options) {
                this._data = { ...this._data, ...options };
                if (this.requestUpdate) this.requestUpdate();
            }
        }

        // Create the chart
        const chart = LightweightCharts.createChart(document.getElementById('chart'), {
            width: window.innerWidth,
            height: 500,
        });

          // Add a line series
          const lineSeries1 = chart.addLineSeries({},0);
        const data1 = [
            { time: '2024-01-01', value: 120 },
            { time: '2024-01-02', value: 130 },
            { time: '2024-01-03', value: 125 },
            { time: '2024-01-04', value: 135 },
            { time: '2024-01-05', value: 140 },
        ];
        lineSeries1.setData(data1);

        // Add a line series
        const lineSeries = chart.addLineSeries({},1);
        const data = [
            { time: '2024-01-01', value: 120 },
            { time: '2024-01-02', value: 130 },
            { time: '2024-01-03', value: 125 },
            { time: '2024-01-04', value: 135 },
            { time: '2024-01-05', value: 140 },
        ];
        lineSeries.setData(data);

        // Add Anchored Text
        const anchoredText = new AnchoredText({
            vertAlign: 'middle',
            horzAlign: 'middle',
            text: 'Anchored Text',
            lineHeight: 54,
            font: 'italic bold 54px Arial',
            color: 'green',
        });
        lineSeries.attachPrimitive(anchoredText);

        // Change the text after 2 seconds
        setTimeout(() => {
            anchoredText.applyOptions({
                text: 'New Text',
            });
        }, 2000);

        // Update chart size on window resize
        window.addEventListener('resize', () => {
            chart.applyOptions({ width: window.innerWidth });
        });
    </script>

@vishnuc
Copy link
Author

vishnuc commented Oct 19, 2024

ok series too works fine with pure js , why dont u use like this in ur plugin,.. so even when u scale it will look good.


   <script type="module">
        import { createChart } from '/tv1.mjs';

        // Renderer for the Lollipop series
        class LollipopSeriesRenderer {
            constructor() {
                this._data = null;
                this._options = null;
            }

            draw(target, priceConverter) {
                target.useBitmapCoordinateSpace(scope => this._drawImpl(scope, priceConverter));
            }

            update(data, options) {
                this._data = data;
                this._options = options;
            }

            _drawImpl(scope, priceToCoordinate) {
                if (!this._data || !this._options || this._data.bars.length === 0 || !this._data.visibleRange) {
                    return;
                }

                const bars = this._data.bars.map(bar => ({
                    x: bar.x,
                    y: priceToCoordinate(bar.originalData.value) ?? 0,
                }));

                const lineWidth = Math.min(this._options.lineWidth, this._data.barSpacing)*3;
                const radius = Math.min(Math.floor(this._data.barSpacing / 2), 5);  // Max radius for the circles
                const zeroY = priceToCoordinate(0);

                for (let i = this._data.visibleRange.from; i < this._data.visibleRange.to; i++) {
                    const bar = bars[i];
                    const xPosition = bar.x * scope.horizontalPixelRatio;
                    const yPosition = bar.y * scope.verticalPixelRatio;

                    scope.context.beginPath();
                    scope.context.fillStyle = this._options.color;
                    
                    // Draw the line from zero to the value
                    scope.context.fillRect(xPosition - lineWidth / 2, zeroY * scope.verticalPixelRatio, lineWidth, yPosition - zeroY * scope.verticalPixelRatio);
                    
                    // Draw the circle on top
                    scope.context.arc(xPosition, yPosition, radius * scope.horizontalPixelRatio, 0, Math.PI * 2);
                    scope.context.fill();
                }
            }
        }

        // Lollipop Series class
        class LollipopSeries {
            constructor() {
                this._renderer = new LollipopSeriesRenderer();
            }

            priceValueBuilder(plotRow) {
                return [0, plotRow.value];
            }

            isWhitespace(data) {
                return data.value === undefined;
            }

            renderer() {
                return this._renderer;
            }

            update(data, options) {
                this._renderer.update(data, options);
            }

            defaultOptions() {
                return {
                    lineWidth: 2,
                    color: 'rgb(0, 100, 255)',
                };
            }
        }

        // Create the chart
        const chart = createChart(document.getElementById('chart'), {
            width: window.innerWidth,
            height: 500,
        });

        // Add Lollipop series to chart
        const customSeriesView = new LollipopSeries();
        const myCustomSeries = chart.addCustomSeries(customSeriesView, {
            lineWidth: 1,   // Make lines thinner
            color: 'rgb(0, 200, 255)',  // Lollipop color
        });

        // Generate random data for the Lollipop series
        function generateLollipopData() {
            const data = [];
            let baseTime = new Date('2021-01-01').getTime() / 1000; // Unix timestamp in seconds

            for (let i = 0; i < 50; i++) {
                const value = Math.random() * 300;  // Use a wider value range
                data.push({
                    time: baseTime + i * 60 * 60 * 24, // Increment by 1 day
                    value: value,
                });
            }
            return data;
        }

        // Set Lollipop data
        const lollipopData = generateLollipopData();
        myCustomSeries.setData(lollipopData);

        // Update chart size on window resize
        window.addEventListener('resize', () => {
            chart.applyOptions({ width: window.innerWidth });
        });
    </script>
    
    

@safaritrader
Copy link

ok series too works fine with pure js , why dont u use like this in ur plugin,.. so even when u scale it will look good.


   <script type="module">
        import { createChart } from '/tv1.mjs';

        // Renderer for the Lollipop series
        class LollipopSeriesRenderer {
            constructor() {
                this._data = null;
                this._options = null;
            }

            draw(target, priceConverter) {
                target.useBitmapCoordinateSpace(scope => this._drawImpl(scope, priceConverter));
            }

            update(data, options) {
                this._data = data;
                this._options = options;
            }

            _drawImpl(scope, priceToCoordinate) {
                if (!this._data || !this._options || this._data.bars.length === 0 || !this._data.visibleRange) {
                    return;
                }

                const bars = this._data.bars.map(bar => ({
                    x: bar.x,
                    y: priceToCoordinate(bar.originalData.value) ?? 0,
                }));

                const lineWidth = Math.min(this._options.lineWidth, this._data.barSpacing)*3;
                const radius = Math.min(Math.floor(this._data.barSpacing / 2), 5);  // Max radius for the circles
                const zeroY = priceToCoordinate(0);

                for (let i = this._data.visibleRange.from; i < this._data.visibleRange.to; i++) {
                    const bar = bars[i];
                    const xPosition = bar.x * scope.horizontalPixelRatio;
                    const yPosition = bar.y * scope.verticalPixelRatio;

                    scope.context.beginPath();
                    scope.context.fillStyle = this._options.color;
                    
                    // Draw the line from zero to the value
                    scope.context.fillRect(xPosition - lineWidth / 2, zeroY * scope.verticalPixelRatio, lineWidth, yPosition - zeroY * scope.verticalPixelRatio);
                    
                    // Draw the circle on top
                    scope.context.arc(xPosition, yPosition, radius * scope.horizontalPixelRatio, 0, Math.PI * 2);
                    scope.context.fill();
                }
            }
        }

        // Lollipop Series class
        class LollipopSeries {
            constructor() {
                this._renderer = new LollipopSeriesRenderer();
            }

            priceValueBuilder(plotRow) {
                return [0, plotRow.value];
            }

            isWhitespace(data) {
                return data.value === undefined;
            }

            renderer() {
                return this._renderer;
            }

            update(data, options) {
                this._renderer.update(data, options);
            }

            defaultOptions() {
                return {
                    lineWidth: 2,
                    color: 'rgb(0, 100, 255)',
                };
            }
        }

        // Create the chart
        const chart = createChart(document.getElementById('chart'), {
            width: window.innerWidth,
            height: 500,
        });

        // Add Lollipop series to chart
        const customSeriesView = new LollipopSeries();
        const myCustomSeries = chart.addCustomSeries(customSeriesView, {
            lineWidth: 1,   // Make lines thinner
            color: 'rgb(0, 200, 255)',  // Lollipop color
        });

        // Generate random data for the Lollipop series
        function generateLollipopData() {
            const data = [];
            let baseTime = new Date('2021-01-01').getTime() / 1000; // Unix timestamp in seconds

            for (let i = 0; i < 50; i++) {
                const value = Math.random() * 300;  // Use a wider value range
                data.push({
                    time: baseTime + i * 60 * 60 * 24, // Increment by 1 day
                    value: value,
                });
            }
            return data;
        }

        // Set Lollipop data
        const lollipopData = generateLollipopData();
        myCustomSeries.setData(lollipopData);

        // Update chart size on window resize
        window.addEventListener('resize', () => {
            chart.applyOptions({ width: window.innerWidth });
        });
    </script>
    
    

that using for loop for redraw too which cant make a different for my current plugin. its executing redraw on resize and changing scales too it just using less code to attach custom shapes or series which is good

@vishnuc
Copy link
Author

vishnuc commented Oct 20, 2024

No yours is not giving native feel , I am checking in my macbook retina..Its easy I think u can create lots of plugin with native way with vanilla js.

also if i scale my pricescale up and down urs is hiding the volume profile etc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants