Setting the crosshair programmatically

Describe the solution you'd like
Setting the crosshair programmatically would be useful in some circumstances. Right now, the crosshair can only be controlled via mouse or touch interactions, but not programmatically, I think (excluding the solution of synthesizing events).
Other user interactions, such as scrolling/panning and changing the scales (once #416 is merged), are already enabled to be controlled programmatically, but control of the crosshair is missing to fully manage the erverything in the UI via client code.
Additional context
Ideally, methods to move the crosshair to a given (canvas) coordinate set or to hide it would be good. If #435 gets implemented too, moving it to a specific time point would also be doable then.
Some areas where this might be useful:

Moving the crosshair in a synchronized fashion on multiple charts as the user brushes over one of them.
Implementing custom crosshair behaviors with non-standard input devices (like, say, on an iPad, the crosshair could be moved directly with the pen, whereas touch is used to scroll/pan/zoom)
Highlighting specific bars by letting the user select items in another UI element, like a list of events
Serializing and restoring UI state, like when live-mirroring the state completely between multiple users, using web sockets or webRTC.

This is already doable to certain extent by synthesizing events (see the gif below), but it doesn't work on touch devices (due to the way events are handled in this case), and deriving the coordinates reliably (if the charts involved aren't sized the same) requires a lot of code, using your dataset, the visible time- and logical ranges, and so on. A proper API would make this way easier.

That would be great!
Also is it possible to share codes of the events that you made in the gif (synthesizing events) ?

It looks like we need to add something like CrossHair API (or kind of). Let's wait more feedback.

@lejyoner-ds My current way of doing it with synthetic events is really more of a hack and works possibly only for my use-case, since I also synchronize the visible range between charts, but I've written some details together in a gist, so that we're not cluttering up this issue thread. I hope you find it useful!
@timocov So regarding how the crosshair API could work, it would be ideal for my use-case(s) to have the following API additions to chart-api:
showCrosshairAtPoint(point: Point): Shows (or moves) the crosshair to the given point in the chart's coordinate system (e.g. same coordinate system as what is currently supplied to MouseEventHandler). If the point falls outside the coordinate system of the chart, the crosshair gets hidden (e.g. it has been moved outside the chart’s area).
hideCrosshair(): Hides the crosshair if it is currently visible, or does nothing if the crosshair is currently not visible.
pointForTime(time: Time): Point | null: Returns the point for a given time value in this chart. The x-coordinate should be the center of the bar, the y-coordinate should be the coordinate the crosshair would snap to if it was set to “magnet” mode. If the time value falls outside the current visibleRange, null is returned. This has its own issue #435 (not opened by me, but fits together nicely).
timeForPoint(point: Point): Time | null: Just the inverse of the pointForTime(), e.g returns the time value for given chart coordinates, or null if the point is either outside the chart’s bounds, or if there is no bar at the given point in the chart. This method (together with the inverse) would be great to be able to easily move the crosshair to the same bar time in multiple different charts (e.g. I get the time for the point in the original chart, then get the point for this time in the other chart, and show the crosshair there).
Optionally, these three methods could be added for some use-cases, e.g. those where people want to implement their own handling of the crosshair (for all three of these, I’m unsure about the best naming):
claimCrosshair(): Disables the “internal” management of the crosshair, e.g. after this is called, the library no longer manages the crosshair, so it doesn’t appear on mouseenter, move on mousemove, disappear on mouseleave, and so on. After calling this, the crosshair is completely controlled by the user only.
unclaimCrosshair(): Symmetric to claimCrosshair(), calling this restores the normal library handling of the crosshair, e.g. the crosshair is no longer managed by the user alone. I think it would also make sense for claim/unclaim calls needing to be balanced, e.g. if I call claimCrosshair() x times, I also need to call unclaimCrosshair() x times to restore normal operation.
chartEventElement(): ChartEventElement: This should return the canvas element for the actual chart, so that users can attach their own event handlers to it. So if I want to implement special handling for the iPad pencil (as an example), I could attach my own handlers for touch events and handle the pencil specially. If I want to take full control of the crosshair, I use this together with claimCrosshair() to disable the default handlers. Since I don’t think it would be good if people expect this method to actually return the canvas (and start to rely on this implementation detail), ChartEventElement could be an interface that only contains addEventListener() and removeEventListener() methods, which behave exactly like the normal DOM methods.
Of course, one could think of allowing multiple crosshairs within the same chart, so that there’s the library-controlled one, and one or more user-controlled one, but I personally don’t think that’s needed.
So that would be my idea – Maybe the others who would be interested in a crosshair API have some feedback/requirements, too!

It would be useful in many cases to support moving the cross-hair smoothly across candles using interpolation (or extrapolation?) when two charts have different time frames.
For example: if one chart uses hourly time scale  (hourly candles) and another chart uses daily time scale (daily candles), then ideally syncing crosshairs between these two charts would allow showing or moving the crosshairs smoothly in both charts (if not snapped to center of the candle) as the user moves moves the mouse in one chart. If user moves mouse in the hourly chart from left to right, it would also move the crosshair in the daily chart as well by the corresponding time scale interval in the daily chart.
At the moment, I do not have access to any example gif or animation of what I am describing, but hope the meaning is clear enough!

Also we need to worry about #50 (crosshair might be on several panes, has different prices/coordinates and so on).

At the moment, I do not have access to any example gif or animation of what I am describing, but hope the meaning is clear enough!

Yeah, we have the same feature on charting library/trading terminal/tradingview's chart itself.

Also we need to worry about #50 (crosshair might be on several panes, has different prices/coordinates and so on).

Oh, good point! I wasn't aware that a public pane API is in the works, I've only seen the concept of a pane used internally so far – Is there a branch that already has a work-in-progress public pane API to look at?
Maybe it would be sufficient then to put the proposed methods on the pane-api, rather than the chart-api then (I suppose the subscriptions for clicks and crosshair updates would also move there).

Yeah, we have the same feature on charting library/trading terminal/tradingview's chart itself.

I think it's also a question of how the "lightweight" in lightweight-charts is interpreted: For use-cases like interpolating the crosshair position from a shorter-timeframe chart/pane over a longer-timeframe chart/pane, one valid approach would be to only provide the most basic primitives and let users implement the actual interpolation behavior themselves, or to provide more "convenience methods" and treat the "lightweight" aspect as "If a representative amount of users need/want the behavior, it gets added to the library natively". So as an example, if there was only the pointForTime() method from my earlier post, the interpolation could be achieved by taking the crosshair time from the source chart, then searching my data in the target chart for the two times that this time falls in between, use pointForTime() for these two times to get the coordinates in the chart, interpolate between these coordinates, and just use showCrosshairAtPoint() to draw the crosshair. It's more work, but it would be doable.
Even if there are more "powerful" methods available, having the primitives around is still great, because people might want to customize certain aspects to fit their particular use-case, which would either require these methods to be rather complex and customizable (like, say, for the interpolation use-case, somebody needed a different interpolation method than linear).
Anyway, since there are already quite a few issues related to crosshair features, we can check them to see if there are use-cases that can't be built, using the proposed primitives.
PS: I'm not arguing that there shouldn't be more advanced/powerful methods available that implement entire behaviors, I'm rather just describing my viewpoint/feedback. I'm always a bit afraid to come across as demanding/opinionated in threads like this.

Is there a branch that already has a work-in-progress public pane API to look at?

Not so far, but we need to keep it in mind to avoid huge breaking changes or even API conflicts.
Also, some converters (like from time/price to coordinate and vice versa) should be done in price/time scale API (actually some of them are already done there - see #435 for instance, but it looks like for price scale we have converters in series but I don't remember exactly reason for that).
Anyway, we'll keep in mind this request, if anybody has additional/specific request for that, leave a comment. If you need it as it - just put your 👍 at the topic message.

any update? possible to use crosshair programatically?

We have a specific use case in mobile devices where we need to close the crosshair when user released their touch from the screen. Apparently some users dislike the the way crosshair sticks and need another tab to close it.
Therefore able to close the crosshair manually/programmatically will come in handy as we can just bind it with onTouchEnd event. Would be even better if we could introduce another mode (stick/non-stick) for crosshair that could achieve to above use case.

Here is my hack/solution/workaround to sync the crosshair between two charts that involve modification of the source code.
It involve a slight modification of 4 file ichart-api.ts,  chart-api.ts, pane-widget.ts and chart-model.ts.
Add this
setCrossHairXY(x: number,y: number,visible: boolean): void;

here
https://github.com/tradingview/lightweight-charts/blob/faa3295f026966cdee84a01c107d090c57b73f6f/src/api/ichart-api.ts#L114
And this
public setCrossHairXY(x: number,y: number,visible: boolean): void{
    this._chartWidget.paneWidgets()[0].setCrossHair(x,y,hide);
}

here
https://github.com/tradingview/lightweight-charts/blob/faa3295f026966cdee84a01c107d090c57b73f6f/src/api/chart-api.ts#L176
And this
public setCrossHair(xx: number,yy: number,visible: boolean): void {
	if (!this._state) {
		return;
	}
	if (visible){
		const x = xx as Coordinate;
		const y = yy as Coordinate;

		if (!mobileTouch) {
			this._setCrosshairPositionNoFire(x, y);
		}
	}else{
		this._state.model().setHoveredSource(null);
		if (!isMobile) {
			this._clearCrosshairPosition();
		}
	}
}

here
https://github.com/tradingview/lightweight-charts/blob/faa3295f026966cdee84a01c107d090c57b73f6f/src/gui/pane-widget.ts#L693
And this
public setAndSaveCurrentPositionFire(x: Coordinate, y: Coordinate, fire: boolean, pane: Pane): void {
	this._crosshair.saveOriginCoord(x, y);
	let price = NaN;
	let index = this._timeScale.coordinateToIndex(x);

	const visibleBars = this._timeScale.visibleStrictRange();
	if (visibleBars !== null) {
		index = Math.min(Math.max(visibleBars.left(), index), visibleBars.right()) as TimePointIndex;
	}

	const priceScale = pane.defaultPriceScale();
	const firstValue = priceScale.firstValue();
	if (firstValue !== null) {
		price = priceScale.coordinateToPrice(y, firstValue);
	}
	price = this._magnet.align(price, index, pane);

	this._crosshair.setPosition(index, price, pane);
	this._cursorUpdate();
	if (fire) {
		this._crosshairMoved.fire(this._crosshair.appliedIndex(), { x, y });
	}
}

here
https://github.com/tradingview/lightweight-charts/blob/faa3295f026966cdee84a01c107d090c57b73f6f/src/model/chart-model.ts#L446
And now you can do something like this .
chart.subscribeCrosshairMove(crosssyncHandler);
function crosssyncHandler(e) {
  if (e.time !== undefined) {
    var xx = chart2.timeScale().timeToCoordinate(e.time);
    chart2.setCrossHairXY(xx,50,true);
  } else if (e.point !== undefined){
    chart2.setCrossHairXY(e.point.x,10,false);
  }
}

chart2.subscribeCrosshairMove(crossSyncHandler2);
function crossSyncHandler2(e) {
  if (e.time !== undefined) {
    var xx = chart.timeScale().timeToCoordinate(e.time);
    chart.setCrossHairXY(xx,200,true);
  } else if (e.point !== undefined){
    chart.setCrossHairXY(e.point.x,100,false);
  }
}

Here is a jsfidle that shows the result
https://jsfiddle.net/trior/y1vcxtqw/
Almost all the code listed above is just a refactored version of already existing code.
Hope it helps a bit.

@triorr Thank you very much for that suggestion and the  code.
You miss to post your custom function this._setCrosshairPositionNoFire(x, y); in the lightweight-charts/src/gui/pane-widget.ts. I assum you disabled the setAndSaveCurrentPosition on lightweight-charts/src/model/chart-model.ts. So I did that as well an it works fine.
I added a clearCrossHair function for leaving a chart, that the cross hair will also remove on the ohter.
public clearCrossHair(): void {
	this._chartWidget.paneWidgets()[0].clearCrossHair();
}

in lightweight-charts/src/api/chart-api.ts under your suggested setCrossHairXY method.
And reister in as well under setCrossHairXY
clearCrossHair(): void;

in lightweight-charts/src/api/ichart-api.ts
When using your code examal it can be extend by:
chart.subscribeCrosshairMove(crosssyncHandler);
let mouseOverChart = false;
function crosssyncHandler(e) {
  if (e.time !== undefined) {
     var xx = chart2.timeScale().timeToCoordinate(e.time);
     chart2.setCrossHairXY(xx,50,true);
   } else if (e.point !== undefined){
     chart2.setCrossHairXY(e.point.x,10,false);
   }  else if(mouseOverChart) {
     mouseOverChart = false;
     chart.clearCrossHair();
   }
}

chart2.subscribeCrosshairMove(crossSyncHandler2);
let mouseOverChart2 = false;
function crossSyncHandler2(e) {
  if (e.time !== undefined) {
    var xx = chart.timeScale().timeToCoordinate(e.time);
    chart.setCrossHairXY(xx,200,true);
  } else if (e.point !== undefined){
    chart.setCrossHairXY(e.point.x,100,false);
  } else if(mouseOverChart2) {
    mouseOverChart2 = false;
    chart.clearCrossHair();
  }
}

Thanks @florian-kittel,
That's true I forgot the _setCrosshairPositionNoFire function .
I modified my post above to correct the error.
My solution is fare from complete.
You can add all sorts of stuff. To make it function correctly in a production area depending on your use case.

I have a problem where I have 4 charts using same data, but deriving multiple timeframes for example: daily, weekly, monthly, and yearly. I want to sync the crosshair horizontal line to the price scale axis.
@triorr what is the best way to do this? I was able to get the charts syncing on the timescale like you showed above.
Thanks

It's working well, that should be added into the library.

Hi Everyone, I get a question & sorry if it's obvious, what's the branch? where I can support for testing.
I'm testing replique the code of @florian-kittel  (example) but only that the function this._cursorUpdate() don't exist. I could support for testing, that's a pleasure for me.

Thanks I was can tested It's nice, it would be add in some feature soon?

Hi Everyone, I get a question & sorry if it's obvious, but what's the branch? where I can support for testing. I'm testing replique the code of @florian-kittel (example) but only that the function this._cursorUpdate() don't exist. I could support for testing, that's a pleasure for me.

Hi, my changes based on v3.4.0. I the later version this._cursorUpdate() is renamed to this.cursorUpdate().

Using trior's patch to sync crosshairs work in desktop-browsers as shown in the video.
But it does not work inside a mobile-based browser.
Can this limitation be bypassed? @triorr @florian-kittel
https://user-images.githubusercontent.com/108040259/189456995-dfc26244-19fd-4e83-8a97-b86aac19d60e.mp4

Using trior's patch to sync crosshairs work in desktop-browsers as shown in the video.
But it does not work inside a mobile-based browser.
Can this limitation be bypassed? @triorr @florian-kittel
screen-20220910-015331.mp4

If you refer to the commit from august, I can not confirm. I still use an older version. But it works on my side for mobile browsers.

https://user-images.githubusercontent.com/10675435/189500879-c3c71d95-01ad-45a6-a922-3ffb8f9d5a27.MOV

FullSizeRender.MOV

Are you using a bluetooth mouse here?
I just tried it on 3.2 and 3.4. Both versions prevent me from moving the crosshair using the cursor (bluetooth mouse).
I can activate the crosshair with a longtap using fingers but the crosshairs didn't sync like yours in the video.
this video is using 3.4 comparing desktop firefox vs mobile browser:
https://user-images.githubusercontent.com/108040259/189521135-d15a1e35-ab14-47fa-8177-98e598d56557.mp4

FullSizeRender.MOV

Are you using a bluetooth mouse here? I just tried it on 3.2 and 3.4. Both versions prevent me from moving the crosshair using the cursor (bluetooth mouse).
I can activate the crosshair with a longtap using fingers but the crosshairs didn't sync like yours in the video.
this video is using 3.4 comparing desktop firefox vs mobile browser:
screen-20220911-123950.mp4

Hi, no I have recoreded the video directly in my iphone 12. It is my PWA and I am using the chart completly with touch. I never tried to use a mouse for the iPhone so I can not say if a mouse hover on mobile device will work. But touch works on my side. (Same goes for iPad, works with touch, never tried mouse on iPad too)

FullSizeRender.MOV

Are you using a bluetooth mouse here? I just tried it on 3.2 and 3.4. Both versions prevent me from moving the crosshair using the cursor (bluetooth mouse).
I can activate the crosshair with a longtap using fingers but the crosshairs didn't sync like yours in the video.
this video is using 3.4 comparing desktop firefox vs mobile browser:
screen-20220911-123950.mp4

Hi, no I have recoreded the video directly in my iphone 12. It is my PWA and I am using the chart completly with touch. I never tried to use a mouse for the iPhone so I can not say if a mouse hover on mobile device will work. But touch works on my side. (Same goes for iPad, works with touch, never tried mouse on iPad too)

I see, I went thru all the versions from the releases and none of them seem to be working for me.
Could I try out your lightweight-charts.standalone.production.js dist?
If that doesn't work, I have another idea for the crosshair but would like to check it just in case. If you don't mind. Thanks

@0x0tyy If I had to guess. I think it's something to do with mobileTouch and isMobile in the setCrossHair  function.
Also you maybe want to take a look at this file support-touch.ts

@0x0tyy If I had to guess. I think it's something to do with mobileTouch and isMobile in the setCrossHair function. Also you maybe want to take a look at this file support-touch.ts

yeah that was the culprit. It is working perfectly in mobile now.

@0x0tyy If I had to guess. I think it's something to do with mobileTouch and isMobile in the setCrossHair function. Also you maybe want to take a look at this file support-touch.ts

how come this file is removed in 3.8.0?

Thanks I was can tested It's nice, it would be add in some feature soon?

Hi, I was wondering how you were able to get these technical analysis on these charts?

You can tested in https://topacio.trade
On Mon, Nov 21, 2022, 12:26 PM Askar @.***> wrote:

Thanks I was can tested It's nice, it would be add in some feature soon? [image:
image]
https://user-images.githubusercontent.com/2575745/140875019-9f58cc17-4580-4182-935a-652189202f88.png
Hi, I was wondering how you were able to get these technical analysis on
these charts?
—
Reply to this email directly, view it on GitHub
https://github.com/tradingview/lightweight-charts/issues/438#issuecomment-1322329404,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AATU3AMNTHSNXTWL5SBSPZLWJOPEFANCNFSM4NCQWYKQ
.
You are receiving this because you are subscribed to this thread.Message
ID: @.***>

@florian-kittel could you please share your clearCrossHair function that should be included in pane-widget.ts ?
@triorr and @0x0tyy - I'm looking to implement this solution/hackaround on lightweight-charts 3.8/4.0. Could you share what you used to set these variables? support-touch.ts is no longer present in the build.

Hi all, I was able to get @triorr's code to work with some slight modifications. See below:
setCrossHairXY(x: number,y: number,visible: boolean): void;
here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/api/ichart-api.ts#L212
(it is line 209 in version 3.8.0)
this:
public setCrosshairXY(x: number,y: number,visible: boolean): void {     this._chartWidget.paneWidgets()[0].setCrosshair(x,y,visible);   }
here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/api/chart-api.ts#L147
(line 193 in version 3.8.0)
this:
` public setCrosshair(xx: number,yy: number,visible: boolean): void {
if (!this._state) {
return;
}
if (visible){
const x = xx as Coordinate;
const y = yy as Coordinate;
  this._setCrosshairPositionNoFire(x, y);
} else {
  this._state.model().setHoveredSource(null);
  this._clearCrosshairPosition();
}

}
private _setCrosshairPositionNoFire(x: Coordinate, y: Coordinate): void {
this._model().setAndSaveCurrentPositionFire(this._correctXCoord(x), this._correctYCoord(y), false, ensureNotNull(this._state));
}`
here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/gui/pane-widget.ts#L659
(line 644 in 3.8.0)
and finally this:
`public setAndSaveCurrentPositionFire(x: Coordinate, y: Coordinate, fire: boolean, pane: Pane): void {
this._crosshair.saveOriginCoord(x, y);
let price = NaN;
let index = this._timeScale.coordinateToIndex(x);
const visibleBars = this._timeScale.visibleStrictRange();
if (visibleBars !== null) {
  index = Math.min(Math.max(visibleBars.left(), index), visibleBars.right()) as TimePointIndex;
}

const priceScale = pane.defaultPriceScale();
const firstValue = priceScale.firstValue();
if (firstValue !== null) {
  price = priceScale.coordinateToPrice(y, firstValue);
}
price = this._magnet.align(price, index, pane);

this._crosshair.setPosition(index, price, pane);
this.cursorUpdate();
if (fire) {
  this._crosshairMoved.fire(this._crosshair.appliedIndex(), { x, y });
}

}`
here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/model/chart-model.ts#L659
(line 658 in 3.8.0)
With the following implementation:
this.mainChart.subscribeCrosshairMove((e) => {         if (e.time !== undefined) {           var xx = this.midChart.timeScale().timeToCoordinate(e.time);           this.midChart.setCrosshairXY(xx,100,true);         } else if (e.point !== undefined){           this.midChart.setCrosshairXY(e.point.x,100,false);         }       });       this.midChart.subscribeCrosshairMove((e) => {         if (e.time !== undefined) {           var xx = this.mainChart.timeScale().timeToCoordinate(e.time);           this.mainChart.setCrosshairXY(xx,100,true);         } else if (e.point !== undefined){           this.mainChart.setCrosshairXY(e.point.x,100,false);         }       });
You can see i made the trivial change renaming setCrossHair to `setCrosshair' to follow the lightweight-chart naming convention.
Also, I removed isMobile and mobileTouch as it seems these are no longer part of the build. As a novice coder, I am uncertain exactly what these were meant to accomplish. It is working well enough for our uses, although there are some things worth changing, such as @florian-kittel's suggestion which I have yet to incorporate (I'm still unsure how!)
Thank you everyone for your help. Hopefully this can assist anyone else looking for this functionality.

Hi all, I was able to get @triorr's code to work with some slight modifications. See below:
setCrossHairXY(x: number,y: number,visible: boolean): void;

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/api/ichart-api.ts#L212
(it is line 209 in version 3.8.0)
this:
public setCrosshairXY(x: number,y: number,visible: boolean): void {
    this._chartWidget.paneWidgets()[0].setCrosshair(x,y,visible);
}

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/api/chart-api.ts#L147
(line 193 in version 3.8.0)
this:
public setCrosshair(xx: number,yy: number,visible: boolean): void {
    if (!this._state) {
      return;
    }
    if (visible){
      const x = xx as Coordinate;
      const y = yy as Coordinate;
      this._setCrosshairPositionNoFire(x, y);
    } else {
      this._state.model().setHoveredSource(null);
      this._clearCrosshairPosition();
    }
  }

private _setCrosshairPositionNoFire(x: Coordinate, y: Coordinate): void {
    this._model().setAndSaveCurrentPositionFire(this._correctXCoord(x), this._correctYCoord(y), false, ensureNotNull(this._state));
}

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/gui/pane-widget.ts#L659
(line 644 in 3.8.0)
and finally this:
public setAndSaveCurrentPositionFire(x: Coordinate, y: Coordinate, fire: boolean, pane: Pane): void {
    this._crosshair.saveOriginCoord(x, y);
    let price = NaN;
    let index = this._timeScale.coordinateToIndex(x);

    const visibleBars = this._timeScale.visibleStrictRange();
    if (visibleBars !== null) {
      index = Math.min(Math.max(visibleBars.left(), index), visibleBars.right()) as TimePointIndex;
    }

    const priceScale = pane.defaultPriceScale();
    const firstValue = priceScale.firstValue();
    if (firstValue !== null) {
      price = priceScale.coordinateToPrice(y, firstValue);
    }
    price = this._magnet.align(price, index, pane);

    this._crosshair.setPosition(index, price, pane);
    this.cursorUpdate();
    if (fire) {
      this._crosshairMoved.fire(this._crosshair.appliedIndex(), { x, y });
    }
 }

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/model/chart-model.ts#L659
(line 658 in 3.8.0)
With the following implementation:
this.mainChart.subscribeCrosshairMove((e) => {
        if (e.time !== undefined) {
          var xx = this.midChart.timeScale().timeToCoordinate(e.time);
          this.midChart.setCrosshairXY(xx,100,true);
        } else if (e.point !== undefined){
          this.midChart.setCrosshairXY(e.point.x,100,false);
        }
      });
      this.midChart.subscribeCrosshairMove((e) => {
        if (e.time !== undefined) {
          var xx = this.mainChart.timeScale().timeToCoordinate(e.time);
          this.mainChart.setCrosshairXY(xx,100,true);
        } else if (e.point !== undefined){
          this.mainChart.setCrosshairXY(e.point.x,100,false);
        }
      });

You can see i made the trivial change renaming setCrossHair to `setCrosshair' to follow the lightweight-chart naming convention.
Also, I removed isMobile and mobileTouch as it seems these are no longer part of the build. As a novice coder, I am uncertain exactly what these were meant to accomplish. It is working well enough for our uses, although there are some things worth changing, such as @florian-kittel's suggestion which I have yet to incorporate (I'm still unsure how!)
Thank you everyone for your help. Hopefully this can assist anyone else looking for this functionality.

Thanks so much for posting your changes, I implemented them and it achieved exactly what I needed! You saved me so much time figuring the changes out myself! Hopefully this api (or something similar) gets merged into the official lib

When I attempt to apply these changes and compile with 'npm run tsc', I get an error that 'ttsc' is not a recognized command. I've checked multiple version, branches and tags, and all contain this command in package.json: ""ttsc -p tsconfig.prod.json"
I was not able to find anything about the ttsc command. What is it and how can i install it ?

Hi all, I was able to get @triorr's code to work with some slight modifications. See below:
setCrossHairXY(x: number,y: number,visible: boolean): void;

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/api/ichart-api.ts#L212
(it is line 209 in version 3.8.0)
this:
public setCrosshairXY(x: number,y: number,visible: boolean): void {
    this._chartWidget.paneWidgets()[0].setCrosshair(x,y,visible);
}

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/api/chart-api.ts#L147
(line 193 in version 3.8.0)
this:
public setCrosshair(xx: number,yy: number,visible: boolean): void {
    if (!this._state) {
      return;
    }
    if (visible){
      const x = xx as Coordinate;
      const y = yy as Coordinate;
      this._setCrosshairPositionNoFire(x, y);
    } else {
      this._state.model().setHoveredSource(null);
      this._clearCrosshairPosition();
    }
  }

private _setCrosshairPositionNoFire(x: Coordinate, y: Coordinate): void {
    this._model().setAndSaveCurrentPositionFire(this._correctXCoord(x), this._correctYCoord(y), false, ensureNotNull(this._state));
}

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/gui/pane-widget.ts#L659
(line 644 in 3.8.0)
and finally this:
public setAndSaveCurrentPositionFire(x: Coordinate, y: Coordinate, fire: boolean, pane: Pane): void {
    this._crosshair.saveOriginCoord(x, y);
    let price = NaN;
    let index = this._timeScale.coordinateToIndex(x);

    const visibleBars = this._timeScale.visibleStrictRange();
    if (visibleBars !== null) {
      index = Math.min(Math.max(visibleBars.left(), index), visibleBars.right()) as TimePointIndex;
    }

    const priceScale = pane.defaultPriceScale();
    const firstValue = priceScale.firstValue();
    if (firstValue !== null) {
      price = priceScale.coordinateToPrice(y, firstValue);
    }
    price = this._magnet.align(price, index, pane);

    this._crosshair.setPosition(index, price, pane);
    this.cursorUpdate();
    if (fire) {
      this._crosshairMoved.fire(this._crosshair.appliedIndex(), { x, y });
    }
 }

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/model/chart-model.ts#L659
(line 658 in 3.8.0)
With the following implementation:
this.mainChart.subscribeCrosshairMove((e) => {
        if (e.time !== undefined) {
          var xx = this.midChart.timeScale().timeToCoordinate(e.time);
          this.midChart.setCrosshairXY(xx,100,true);
        } else if (e.point !== undefined){
          this.midChart.setCrosshairXY(e.point.x,100,false);
        }
      });
      this.midChart.subscribeCrosshairMove((e) => {
        if (e.time !== undefined) {
          var xx = this.mainChart.timeScale().timeToCoordinate(e.time);
          this.mainChart.setCrosshairXY(xx,100,true);
        } else if (e.point !== undefined){
          this.mainChart.setCrosshairXY(e.point.x,100,false);
        }
      });

You can see i made the trivial change renaming setCrossHair to setCrosshair' to follow the lightweight-chart naming convention. Also, I removed isMobileandmobileTouch` as it seems these are no longer part of the build. As a novice coder, I am uncertain exactly what these were meant to accomplish. It is working well enough for our uses, although there are some things worth changing, such as @florian-kittel's suggestion which I have yet to incorporate (I'm still unsure how!)
Thank you everyone for your help. Hopefully this can assist anyone else looking for this functionality.

Thanks so much for posting your changes, I implemented them and it achieved exactly what I needed! You saved me so much time figuring the changes out myself! Hopefully this api (or something similar) gets merged into the official lib

Would you be able to share you steps to build and deploy/use the built packages ?
I've ran " npm run build " and it produced the following files:
It produces the following files:
lightweight-charts.esm.development.js
lightweight-charts.esm.production.js
lightweight-charts.standalone.development.js
lightweight-charts.standalone.production.js
typings.d.ts
The original package contains the following package.json:
{
"version": "4.0.1",
"name": "lightweight-charts",
"author": "TradingView, Inc.",
"license": "Apache-2.0",
"description": "Performant financial charts built with HTML5 canvas",
"bugs": {
"url": "https://github.com/tradingview/lightweight-charts/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/tradingview/lightweight-charts.git"
},
"module": "dist/lightweight-charts.production.mjs",
"main": "index.cjs",
"typings": "dist/typings.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": {
"types": "./dist/typings.d.ts",
"import": "./dist/lightweight-charts.development.mjs",
"require": "./dist/lightweight-charts.development.cjs"
},
"production": {
"types": "./dist/typings.d.ts",
"import": "./dist/lightweight-charts.production.mjs",
"require": "./dist/lightweight-charts.production.cjs"
},
"default": {
"types": "./dist/typings.d.ts",
"import": "./dist/lightweight-charts.production.mjs",
"require": "./index.cjs"
}
}
},
"files": [
"dist/**"
],
"keywords": [
"financial-charting-library",
"charting-library",
"html5-charts",
"canvas",
"typescript",
"charting",
"charts"
],
"dependencies": {
"fancy-canvas": "2.1.0"
}
}
, and the following files:
lightweight-charts.development.cjs
lightweight-charts.development.mjs
lightweight-charts.production.cjs
lightweight-charts.production.mjs
lightweight-charts.standalone.development.js
lightweight-charts.standalone.development.mjs
lightweight-charts.standalone.production.js
lightweight-charts.standalone.production.mjs
typings.d.ts
Can you share your package.json that you use to use the custom-built files ?

Hi all, I was able to get @triorr's code to work with some slight modifications. See below:
setCrossHairXY(x: number,y: number,visible: boolean): void;

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/api/ichart-api.ts#L212
(it is line 209 in version 3.8.0)
this:
public setCrosshairXY(x: number,y: number,visible: boolean): void {
    this._chartWidget.paneWidgets()[0].setCrosshair(x,y,visible);
}

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/api/chart-api.ts#L147
(line 193 in version 3.8.0)
this:
public setCrosshair(xx: number,yy: number,visible: boolean): void {
    if (!this._state) {
      return;
    }
    if (visible){
      const x = xx as Coordinate;
      const y = yy as Coordinate;
      this._setCrosshairPositionNoFire(x, y);
    } else {
      this._state.model().setHoveredSource(null);
      this._clearCrosshairPosition();
    }
  }

private _setCrosshairPositionNoFire(x: Coordinate, y: Coordinate): void {
    this._model().setAndSaveCurrentPositionFire(this._correctXCoord(x), this._correctYCoord(y), false, ensureNotNull(this._state));
}

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/gui/pane-widget.ts#L659
(line 644 in 3.8.0)
and finally this:
public setAndSaveCurrentPositionFire(x: Coordinate, y: Coordinate, fire: boolean, pane: Pane): void {
    this._crosshair.saveOriginCoord(x, y);
    let price = NaN;
    let index = this._timeScale.coordinateToIndex(x);

    const visibleBars = this._timeScale.visibleStrictRange();
    if (visibleBars !== null) {
      index = Math.min(Math.max(visibleBars.left(), index), visibleBars.right()) as TimePointIndex;
    }

    const priceScale = pane.defaultPriceScale();
    const firstValue = priceScale.firstValue();
    if (firstValue !== null) {
      price = priceScale.coordinateToPrice(y, firstValue);
    }
    price = this._magnet.align(price, index, pane);

    this._crosshair.setPosition(index, price, pane);
    this.cursorUpdate();
    if (fire) {
      this._crosshairMoved.fire(this._crosshair.appliedIndex(), { x, y });
    }
 }

here:
https://github.com/tradingview/lightweight-charts/blob/e263d724aa135da699f744b152422b94f094aa28/src/model/chart-model.ts#L659
(line 658 in 3.8.0)
With the following implementation:
this.mainChart.subscribeCrosshairMove((e) => {
        if (e.time !== undefined) {
          var xx = this.midChart.timeScale().timeToCoordinate(e.time);
          this.midChart.setCrosshairXY(xx,100,true);
        } else if (e.point !== undefined){
          this.midChart.setCrosshairXY(e.point.x,100,false);
        }
      });
      this.midChart.subscribeCrosshairMove((e) => {
        if (e.time !== undefined) {
          var xx = this.mainChart.timeScale().timeToCoordinate(e.time);
          this.mainChart.setCrosshairXY(xx,100,true);
        } else if (e.point !== undefined){
          this.mainChart.setCrosshairXY(e.point.x,100,false);
        }
      });

You can see i made the trivial change renaming setCrossHair to `setCrosshair' to follow the lightweight-chart naming convention.
Also, I removed isMobile and mobileTouch as it seems these are no longer part of the build. As a novice coder, I am uncertain exactly what these were meant to accomplish. It is working well enough for our uses, although there are some things worth changing, such as @florian-kittel's suggestion which I have yet to incorporate (I'm still unsure how!)
Thank you everyone for your help. Hopefully this can assist anyone else looking for this functionality.

Thank you for the method you provided. I am using version 4.0, and I cannot use your code of version 3.8. I don’t understand the internal principle of lightweight, and I don’t know how to modify it. Is there a corresponding repair method in version 4.0?

@silsuer I'm using it with 4.0 via a branch using these changes
https://github.com/tradingview/lightweight-charts/commit/fadc845745b568baef5d9ecb5470e00814f6b5ff
Let me know how that works for you

Below is how I'm using this in the js file. It assumes an object charts that contains three charts: main, aux1, and aux2

syncCrosshairs() {
    this.charts.main.subscribeCrosshairMove(this.mainCrosshairHandler);
    Object.keys(this.charts).filter(chart => chart !== 'main').forEach(chart => {
      this.charts[chart].subscribeCrosshairMove(this.auxCrosshairHandler);
    });
	}

  desyncCrosshairs() {
    this.charts.main.unsubscribeCrosshairMove(this.mainCrosshairHandler);
    Object.keys(this.charts).filter(chart => chart !== 'main').forEach(chart => {
      this.charts[chart].unsubscribeCrosshairMove(this.auxCrosshairHandler);
    });
	}

  resyncCrosshairs() {
    this.desyncCrosshairs();
    this.syncCrosshairs()
  }

  mainCrosshairHandler = (e) => {
    if (e.time !== undefined) {
      Object.keys(this.charts).filter(chart => chart !== 'main').forEach((chart) => {
        this.charts[chart].setCrosshairXY(this.charts.main.timeScale().timeToCoordinate(e.time), 10000, true);
      });
    } else if (e.point !== undefined) {
      Object.keys(this.charts).filter(chart => chart !== 'main').forEach((chart) => {
        this.charts[chart].setCrosshairXY(e.point.x, 10000, false);
      });
    }
    this.subLegend(e);
  }

  auxCrosshairHandler = (e) => {
    if (e.time !== undefined) {
      Object.keys(this.charts).forEach(chart => {
        this.charts[chart].setCrosshairXY(this.charts.aux1.timeScale().timeToCoordinate(e.time), 10000, true);
      });
    } else if (e.point !== undefined) {
      Object.keys(this.charts).forEach(chart => {
        this.charts[chart].setCrosshairXY(e.point.x, 10000, false);
      });
    }
    this.subLegend(e);
  }

I am using lightweight-chart version 4.0.1 but still setCrosshairXY is not a function error occurs. does this mean I have to modify the package? where can I find forked one which maintains this feature?

Version 4.1 includes a method for setting the crosshair position.
https://tradingview.github.io/lightweight-charts/tutorials/how_to/set-crosshair-position

@SlicedSilver  - jumping off this as to not create extra noise and extra issues.
Can setCrosshairPosition be used to persist a crosshair even after user unhovers/ineracts with chart? I need crosshair to persist for a number of seconds.

Can setCrosshairPosition be used to persist a crosshair even after user unhovers/ineracts with chart? I need crosshair to persist for a number of seconds.

Yes, this would be possible. It allows you to set the crosshair location independently from any touch or mouse event.

@SlicedSilver I've tried syncing two charts with 'Set crosshair position,' but it seems that it's not synchronizing properly.
https://github.com/tradingview/lightweight-charts/assets/61302262/0153658f-cc6a-4b78-ba30-23759b1179d9

It appears to be syncing correctly. The date and time within the timescale labels (for the crosshair) are showing very similar times. It may not be identical times if the two series / charts don't have the exact same timestamps present within their datasets.
I assume that you many concern is that the crosshair line doesn't appear to be continuous across both charts. I would suggest that you try match the visible time range in both charts (using setVisibleRange or setVisibleLogicalRange). Ideally if both datasets had the same timestamps then you would get even better results.
If this doesn't help or answer your query then could you describe the issue in a bit more detail, and if possible provide a code sample?

@SlicedSilver - I have multiple charts and each chart has multi lines
From this,  https://tradingview.github.io/lightweight-charts/tutorials/how_to/set-crosshair-position I can able to sync only 2 charts.
Actual: I can able to sync two charts as per doc. But not able to sync tooltip.
Expected: I wants to sync cross hair and tooltip more than 2 charts dynamically. Expected like below

The example only shows 2 charts but the concept can be extended to any number of charts. Also it is possible to use zoom and scrolling when using this example.
Did you add this code?
chart1.timeScale().subscribeVisibleLogicalRangeChange(timeRange => {
    chart2.timeScale().setVisibleLogicalRange(timeRange);
});

chart2.timeScale().subscribeVisibleLogicalRangeChange(timeRange => {
    chart1.timeScale().setVisibleLogicalRange(timeRange);
});

For all of this to work nicely it is recommended that both charts have the same number of data points (and the same timestamps).

yes,  I have used same concept,  and able to sync it. but after syncing zoom is not working (scroll works fine).
But my concern here how to handle it dynamically? if tmrw Have to add 4th chart means then we want to manually add it and sync it here. instead if we based on dataset have to create multi charts like that reusuable component am trying.
import { ASIA_KOLKATA_UTC, DATE_FORMAT_1, NUMERICAL_CONVERSION } from "utils/constants";
import { chartListType, chartRecordType } from "types/Interfaces";
import { ColorType, createChart } from "lightweight-charts";
import { getFormatDate, getPrecision, getTimeStamp, isEmpty, styleVariable } from "utils/UtilityFunc";
import React, { useEffect, useState } from "react";
interface MultichartsProps {
chartName: string
dataList: chartListType[]
showLeftPriceScale?: boolean
timeVisible?: boolean
}
const MultiCharts = (props: MultichartsProps) => {
const { chartName, dataList } = props;

const [
    totalSet, setTotalSet
] = useState<any>({});

const [
    chartOneDataSet, setChartOneDataSet
] = useState<any[]>([
]);

const [
    chartTwoDataSet, setChartTwoDataSet
] = useState<any[]>([
]);

const [
    chartThreeDataSet, setChartThreeDataSet
] = useState<any[]>([
]);

useEffect(() => {
    console.log("chartName :", chartName, dataList);

    const combinedData = [
        ...dataList
    ];

    const combinedDataObject = combinedData.reduce((acc: any, obj) => {
        const newObj = { ...obj };
        newObj.records = obj.records.reduce((recordAcc: any, record: any) => {
            recordAcc[ record.time ] = { value: record.value, time: record.time };
            return recordAcc;
        }, {});

        acc[ obj.key as string ] = newObj;

        return acc;
    }, {});

    setTotalSet(combinedDataObject);
    setChartTwoDataSet([
        combinedDataObject.IVP
    ]);
    setChartOneDataSet([
        combinedDataObject.IV, combinedDataObject.HV10, combinedDataObject.HV30, combinedDataObject[ "IV-RV" ]
    ]);
    setChartThreeDataSet([
        combinedDataObject.PRICE
    ]);

}, [
    dataList
]);

let chart1: any = null;
let lineSeries1: any = null;

let chart2: any = null;
let lineSeries2: any = null;

let chart3: any = null;
let lineSeries3: any = null;

const getTooltipTime = (time: number) => {
    const date = getTimeStamp(time - ASIA_KOLKATA_UTC);
    return getFormatDate(date, DATE_FORMAT_1);
};

function getTooltip(elemContainer: any, itemList: any, chartId: string, param: any) {
    console.log("elemContainer :", elemContainer, itemList, chartId, param);
    let toolTipWidth = 100, toolTipHeight = 100;
    const toolTipMargin = 15;

    const toolTip = document.createElement("div");
    toolTip.id = `light-tooltip-${chartId}`;
    toolTip.className = "light-tooltip";
    const toolTipElement = document.getElementById(`light-tooltip-${chartId}`) as HTMLElement;
    if (toolTipElement) elemContainer.removeChild(toolTipElement);
    elemContainer.appendChild(toolTip);

    if (isEmpty(param.point)) {
        toolTip.style.display = "none";
        return;
    }

    const positionX = param.point.x;
    const positionY = param.point.y;

    if (isEmpty(param.point) || !param.time ||
        positionX < 0 || positionX > elemContainer.clientWidth ||
        positionY < 0 || positionY > elemContainer.clientHeight) {
        toolTip.style.display = "none";
    } else {
        // time will be in the same format that we supplied to setData.
        // thus it will be YYYY-MM-DD
        toolTip.style.display = "block";

        toolTip.innerHTML = `<div class="time">${getTooltipTime(param.time)}</div>
        ${itemList.map((item: any) => {

    if (!item.visible)
        return "";
    console.log("item.key", item.key, totalSet[ item.key ]);

    if (!totalSet[ item.key ].records[ param.time - ASIA_KOLKATA_UTC ])
        return "";

    return `<div class="record">
    <div class="tooltip-label"> ${item.label}: </div>
    <div class="tooltip-value" style="color: ${item.props.color}">
    ${getPrecision(
    totalSet[ item.key ].records[ param.time - ASIA_KOLKATA_UTC ].value,
    item.precision ? item.precision : 0
)}
</div>
    </div>`;
}).join(" ")}`;

        const toolTipEle = document.getElementById(`light-tooltip-${chartId}`) as HTMLElement;
        toolTipWidth = toolTipEle.offsetWidth;
        toolTipHeight = toolTipEle.offsetHeight;

        let leftPriceScaleWidth = 0;
        const showLeftPriceScale = false;

        if (chart1 && showLeftPriceScale) {
            leftPriceScaleWidth = chart1.priceScale("left").width();
        }

        let leftPosition = positionX + leftPriceScaleWidth + toolTipMargin;
        let rightPosition: string | number = "auto";

        if (leftPosition > elemContainer.clientWidth - toolTipWidth) {
            rightPosition = elemContainer.clientWidth - positionX - leftPriceScaleWidth + toolTipMargin;
            leftPosition = "auto";
        }

        let topPosition = positionY + toolTipMargin;
        if (topPosition > elemContainer.clientHeight - toolTipHeight) {
            topPosition = positionY - toolTipMargin - toolTipHeight;
        }
        toolTip.style.left = leftPosition === "auto" ? "auto" : `${leftPosition}px`;
        toolTip.style.top = `${topPosition}px`;
        toolTip.style.right = rightPosition === "auto" ? "auto" : `${rightPosition}px`;
    }
}

function getCrosshairDataPoint(series: any, param: any) {
    if (!param.time) {
        return null;
    }
    const dataPoint = param.seriesData.get(series);
    return dataPoint || null;
}

function syncCrosshair(chart: any, series: any, dataPoint: any) {
    if (dataPoint && chart && chart.setCrosshairPosition) {
        chart.setCrosshairPosition(dataPoint.value, dataPoint.time, series);
        return;
    }

    if (chart && chart.clearCrosshairPosition)
        chart.clearCrosshairPosition();
}

const removeDuplicate = (record: chartRecordType[]) => {
    return record.filter((value: chartRecordType, inx: number, list: chartRecordType[]) => {
        return inx === list.findIndex((ele) => {
            return ele.time === value.time;
        });
    });
};

function getOnMouseValue(time: number, type: string) {
    if (totalSet[ type ].records[ time - ASIA_KOLKATA_UTC ])
        return totalSet[ type ].records[ time - ASIA_KOLKATA_UTC ].value;
    return "";
}

useEffect(() => {

    const container1 = document.getElementById("chartOne") as HTMLElement;
    const container2 = document.getElementById("chartTwo") as HTMLElement;
    const container3 = document.getElementById("chartThree") as HTMLElement;

    if (chartOneDataSet.length && chartTwoDataSet.length && chartThreeDataSet.length) {
        chart1 = createChart(container1, {
            height: 400
        });

        chart1.applyOptions({
            autoSize: true,
            layout: {
                textColor: styleVariable("--header-text"),
                background: {
                    type: "solid" as ColorType.Solid,
                    color: "transparent"
                },
                fontSize: 13
            },
            grid: {
                vertLines: { color: styleVariable("--chart-grid-vert") },
                horzLines: { color: styleVariable("--chart-grid-horz") }
            },
            crosshair: {
                mode: 0,
                horzLine: {
                    color: styleVariable("--primary-color"),
                    labelBackgroundColor: styleVariable("--primary-color"),
                },
                vertLine: {
                    labelBackgroundColor: styleVariable("--primary-color"),
                }
            },
            rightPriceScale: {
                visible: false,
            },
            leftPriceScale: {
                visible: true,
            },
            timeScale: {
                timeVisible: false,
                secondsVisible: false
            },
            handleScale: false
        });

        chartOneDataSet.forEach((item: any, index: any) => {
            lineSeries1 = chart1[ item.type ]({
                visible: item.visible,
                lineWidth: 1,
                lastValueVisible: false, priceLineVisible: false,
                priceFormat: {
                    type: item.formatType,
                    precision: item.formatType === "" ? 0 : item.precision ? item.precision : 2,
                    minMove: item.formatType === "" ? 1 : 0.01,
                    formatter: function (price: number) {
                        return `${(price / 100000).toFixed(2) } ${NUMERICAL_CONVERSION.LAKH}`;
                    },
                },
                ...item.props,
                priceScaleId: item.props.priceScaleId
                    ? item.props.priceScaleId :
                    index === 0 ? "left" : index === 1 ? "right" : ""
            });

            const updated = removeDuplicate(Object.values(item.records).map((row: any) => {
                row.time = row.time + ASIA_KOLKATA_UTC;
                return row;
            }));

            lineSeries1.setData(updated);
            console.log("updated :", updated);

            item.seriesKey = lineSeries1;

        });

        chart1.timeScale().subscribeVisibleLogicalRangeChange((timeRange: any) => {
            if (chart2)
                chart2.timeScale().setVisibleLogicalRange(timeRange as any);
            if (chart3)
                chart3.timeScale().setVisibleLogicalRange(timeRange as any);
        });

        chart1.subscribeCrosshairMove((param: any) => {
            const dataPoint = getCrosshairDataPoint(lineSeries1, param);
            syncCrosshair(chart2, lineSeries2, dataPoint);
            syncCrosshair(chart3, lineSeries3, dataPoint);
            if (dataPoint && dataPoint.time && lineSeries1 && lineSeries2 && chart1 && chart2 && chart3) {
                const y1 = lineSeries1.priceToCoordinate(dataPoint.value);
                const y2 = lineSeries2.priceToCoordinate(getOnMouseValue(dataPoint.time, "IVP" ));
                const y3 = lineSeries3.priceToCoordinate(getOnMouseValue(dataPoint.time, "PRICE" ));
                console.log("y3 :", y3);
                const x1 = chart1.timeScale().timeToCoordinate(dataPoint.time);
                const x2 = chart2.timeScale().timeToCoordinate(dataPoint.time);
                const x3 = chart2.timeScale().timeToCoordinate(dataPoint.time);
                const params1: any = {
                    point: {
                        x: x1,
                        y: y1
                    },
                    time: dataPoint.time
                };

                const params2: any = {
                    point: {
                        x: x2,
                        y: y2
                    },
                    time: dataPoint.time
                };

                const params3: any = {
                    point: {
                        x: x3,
                        y: y3
                    },
                    time: dataPoint.time
                };

                getTooltip(container1, chartOneDataSet, "chartOne", params1);
                getTooltip(container2, chartTwoDataSet, "chartTwo", params2);
                getTooltip(container3, chartThreeDataSet, "chartThree", params3);
            } else {
                const params1: any = {
                };

                const params2: any = {
                };

                const params3: any = {
                };

                getTooltip(container1, chartOneDataSet, "chartOne", params1);
                getTooltip(container2, chartTwoDataSet, "chartTwo", params2);
                getTooltip(container3, chartThreeDataSet, "chartThree", params3);
            }

        });

        chart2 = createChart(container2, {
            height: 400,
        });

        chart2.applyOptions({
            autoSize: true,
            layout: {
                textColor: styleVariable("--chart-text"),
                fontSize: 12,
                fontFamily: "NxtOption-Regular",
                background: {
                    type: "solid" as ColorType.Solid,
                    color: "transparent"
                }
            },
            grid: {
                vertLines: { color: styleVariable("--chart-grid-vert") },
                horzLines: { color: styleVariable("--chart-grid-horz") }
            },
            crosshair: {
                mode: 0,
                horzLine: {
                    color: styleVariable("--primary-color"),
                    labelBackgroundColor: styleVariable("--primary-color"),
                },
                vertLine: {
                    labelBackgroundColor: styleVariable("--primary-color"),
                }
            },
            rightPriceScale: {
                visible: false,
            },
            leftPriceScale: {
                visible: true,
            },
            timeScale: {
                timeVisible: false,
                secondsVisible: false
            },
            handleScale: false
        });

        chartTwoDataSet.forEach((item: any, index: any) => {
            lineSeries2 = chart2[ item.type ]({
                visible: item.visible,
                lineWidth: 1,
                lastValueVisible: false, priceLineVisible: false,
                priceFormat: {
                    type: item.formatType,
                    precision: item.formatType === "" ? 0 : item.precision ? item.precision : 2,
                    minMove: item.formatType === "" ? 1 : 0.01,
                    formatter: function (price: number) {
                        return `${(price / 100000).toFixed(2) } ${NUMERICAL_CONVERSION.LAKH}`;
                    },
                },
                ...item.props,
                priceScaleId: item.props.priceScaleId
                    ? item.props.priceScaleId :
                    index === 0 ? "left" : index === 1 ? "right" : ""
            });

            const updated = removeDuplicate(Object.values(item.records).map((row: any) => {
                row.time = row.time + ASIA_KOLKATA_UTC;
                return row;
            }));

            lineSeries2.setData(updated);

            item.seriesKey = lineSeries2;
        });

        chart2.timeScale().subscribeVisibleLogicalRangeChange((timeRange: any) => {
            if (chart1)
                chart1.timeScale().setVisibleLogicalRange(timeRange as any);
            if (chart3)
                chart3.timeScale().setVisibleLogicalRange(timeRange as any);
        });

        chart2.subscribeCrosshairMove((param: any) => {
            const dataPoint = getCrosshairDataPoint(lineSeries2, param);
            console.log("lineSeries2 :", lineSeries2);
            syncCrosshair(chart1, lineSeries1, dataPoint);
            syncCrosshair(chart3, lineSeries3, dataPoint);
            if (dataPoint && dataPoint.time && lineSeries1 && lineSeries2 && chart1 && chart2 && chart3) {
                const y1 = lineSeries1.priceToCoordinate(getOnMouseValue(dataPoint.time, "IV" ));
                const y2 = lineSeries2.priceToCoordinate(dataPoint.value);
                const y3 = lineSeries3.priceToCoordinate(getOnMouseValue(dataPoint.time, "PRICE" ));
                const x1 = chart1.timeScale().timeToCoordinate(dataPoint.time);
                const x2 = chart2.timeScale().timeToCoordinate(dataPoint.time);
                const x3 = chart2.timeScale().timeToCoordinate(dataPoint.time);

                const params1: any = {
                    point: {
                        x: x1,
                        y: y1
                    },
                    time: dataPoint.time
                };

                const params2: any = {
                    point: {
                        x: x2,
                        y: y2
                    },
                    time: dataPoint.time
                };

                const params3: any = {
                    point: {
                        x: x3,
                        y: y3
                    },
                    time: dataPoint.time
                };

                getTooltip(container1, chartOneDataSet, "chartOne", params1);
                getTooltip(container2, chartTwoDataSet, "chartTwo", params2);
                getTooltip(container3, chartThreeDataSet, "chartThree", params3);
            } else {
                const params1: any = {
                };

                const params2: any = {
                };

                const params3: any = {
                };

                getTooltip(container1, chartOneDataSet, "chartOne", params1);
                getTooltip(container2, chartTwoDataSet, "chartTwo", params2);
                getTooltip(container3, chartThreeDataSet, "chartThree", params3);
            }
        });

        chart3 = createChart(container3, {
            height: 400
        });

        chart3.applyOptions({
            autoSize: true,
            layout: {
                textColor: styleVariable("--header-text"),
                background: {
                    type: "solid" as ColorType.Solid,
                    color: "transparent"
                },
                fontSize: 13
            },
            grid: {
                vertLines: { color: styleVariable("--chart-grid-vert") },
                horzLines: { color: styleVariable("--chart-grid-horz") }
            },
            crosshair: {
                mode: 0,
                horzLine: {
                    color: styleVariable("--primary-color"),
                    labelBackgroundColor: styleVariable("--primary-color"),
                },
                vertLine: {
                    labelBackgroundColor: styleVariable("--primary-color"),
                }
            },
            rightPriceScale: {
                visible: false,
            },
            leftPriceScale: {
                visible: true,
            },
            timeScale: {
                timeVisible: false,
                secondsVisible: false
            },
            handleScale: false
        });

        console.log("chartThreeDataSet :", chartThreeDataSet);
        chartThreeDataSet.forEach((item: any, index: any) => {
            console.log("chartThreeDataSet item :", item);
            lineSeries3 = chart3[ item.type ]({
                visible: item.visible,
                lineWidth: 1,
                lastValueVisible: false, priceLineVisible: false,
                priceFormat: {
                    type: item.formatType,
                    precision: item.formatType === "" ? 0 : item.precision ? item.precision : 2,
                    minMove: item.formatType === "" ? 1 : 0.01,
                    formatter: function (price: number) {
                        return `${(price / 100000).toFixed(2) } ${NUMERICAL_CONVERSION.LAKH}`;
                    },
                },
                ...item.props,
                priceScaleId: item.props.priceScaleId
                    ? item.props.priceScaleId :
                    index === 0 ? "left" : index === 1 ? "right" : ""
            });

            const updated = removeDuplicate(Object.values(item.records).map((row: any) => {
                row.time = row.time + ASIA_KOLKATA_UTC;
                return row;
            }));

            lineSeries3.setData(updated);

            item.seriesKey = lineSeries3;

        });

        chart3.timeScale().subscribeVisibleLogicalRangeChange((timeRange: any) => {
            if (chart2)
                chart2.timeScale().setVisibleLogicalRange(timeRange as any);
            if (chart1)
                chart1.timeScale().setVisibleLogicalRange(timeRange as any);
        });

        chart3.subscribeCrosshairMove((param: any) => {
            const dataPoint = getCrosshairDataPoint(lineSeries3, param);
            syncCrosshair(chart2, lineSeries2, dataPoint);
            syncCrosshair(chart1, lineSeries1, dataPoint);
            if (dataPoint && dataPoint.time && lineSeries1 && lineSeries2 && lineSeries3 &&
                 chart1 && chart2 && chart3) {
                const y1 = lineSeries1.priceToCoordinate(getOnMouseValue(dataPoint.time, "IV"));
                const y2 = lineSeries2.priceToCoordinate(getOnMouseValue(dataPoint.time, "IVP" ));
                const y3 = lineSeries3.priceToCoordinate(dataPoint.value);
                const x1 = chart1.timeScale().timeToCoordinate(dataPoint.time);
                const x2 = chart2.timeScale().timeToCoordinate(dataPoint.time);
                const x3 = chart2.timeScale().timeToCoordinate(dataPoint.time);
                const params1: any = {
                    point: {
                        x: x1,
                        y: y1
                    },
                    time: dataPoint.time
                };

                const params2: any = {
                    point: {
                        x: x2,
                        y: y2
                    },
                    time: dataPoint.time
                };

                const params3: any = {
                    point: {
                        x: x3,
                        y: y3
                    },
                    time: dataPoint.time
                };

                getTooltip(container1, chartOneDataSet, "chartOne", params1);
                getTooltip(container2, chartTwoDataSet, "chartTwo", params2);
                getTooltip(container3, chartThreeDataSet, "chartThree", params3);
            } else {
                const params1: any = {
                };

                const params2: any = {
                };

                const params3: any = {
                };

                getTooltip(container1, chartOneDataSet, "chartOne", params1);
                getTooltip(container2, chartTwoDataSet, "chartTwo", params2);
                getTooltip(container3, chartThreeDataSet, "chartThree", params3);
            }

        });

        chart1.timeScale().fitContent();
        chart2.timeScale().fitContent();
        chart3.timeScale().fitContent();
    }

    return () => {
        if (chart1)
            chart1.remove();
        if (chart2)
            chart2.remove();
        if (chart3)
            chart3.remove();
    };
}, [
    chartTwoDataSet, chartOneDataSet, chartThreeDataSet
]);

return (
    <>
        <div className="lightweight-multiple-charts" id={"chartOne"}></div>
        <div className="lightweight-multiple-charts2" id={"chartTwo"}></div>
        <div className="lightweight-multiple-charts3" id={"chartThree"}></div>
    </>

);

};
export default MultiCharts;

Could you please create an example on an online code editor like JSfiddle / Codepen / CodeSandbox / ...?

Here is the link https://codesandbox.io/p/sandbox/chart-js-react-typescript-forked-mrzchx?file=%2Fsrc%2Fstyles.css%3A4%2C1&layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clvy0gjzm0006356l46dfd8ab%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clvy0gjzm0002356lmlg67iut%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clvy0gjzm0003356lisrmn50m%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clvy0gjzm0005356ldq9iogin%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B47.01092163249665%252C52.98907836750335%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clvy0gjzm0002356lmlg67iut%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clvy0gjzl0001356lz3lbadka%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252Findex.tsx%2522%252C%2522state%2522%253A%2522IDLE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A2%252C%2522startColumn%2522%253A12%252C%2522endLineNumber%2522%253A2%252C%2522endColumn%2522%253A12%257D%255D%257D%252C%257B%2522id%2522%253A%2522clvy0huwz003i356lnmb86y3i%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A3%252C%2522startColumn%2522%253A22%252C%2522endLineNumber%2522%253A3%252C%2522endColumn%2522%253A22%257D%255D%252C%2522filepath%2522%253A%2522%252Fpackage.json%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522id%2522%253A%2522clvy24a610002356lsbig26ld%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A683%252C%2522startColumn%2522%253A1%252C%2522endLineNumber%2522%253A683%252C%2522endColumn%2522%253A1%257D%255D%252C%2522filepath%2522%253A%2522%252Fsrc%252FMultiCharts.tsx%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522id%2522%253A%2522clvy27gcv0002356l66b34scu%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A9%252C%2522startColumn%2522%253A1%252C%2522endLineNumber%2522%253A9%252C%2522endColumn%2522%253A1%257D%255D%252C%2522filepath%2522%253A%2522%252Fsrc%252FApp.tsx%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522id%2522%253A%2522clvy2fmzb0002356l280xl0tr%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A4%252C%2522startColumn%2522%253A1%252C%2522endLineNumber%2522%253A4%252C%2522endColumn%2522%253A1%257D%255D%252C%2522filepath%2522%253A%2522%252Fsrc%252Fstyles.css%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clvy0gjzm0002356lmlg67iut%2522%252C%2522activeTabId%2522%253A%2522clvy2fmzb0002356l280xl0tr%2522%257D%252C%2522clvy0gjzm0005356ldq9iogin%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clvy0gjzm0004356lslo14a3d%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A0%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522clvy0gjzm0005356ldq9iogin%2522%252C%2522activeTabId%2522%253A%2522clvy0gjzm0004356lslo14a3d%2522%257D%252C%2522clvy0gjzm0003356lisrmn50m%2522%253A%257B%2522tabs%2522%253A%255B%255D%252C%2522id%2522%253A%2522clvy0gjzm0003356lisrmn50m%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Afalse%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D

@SlicedSilver  - Any update on this? as suggested have created a codesandbox, plz provide a solution.

Here is an example with multiple charts which are synced together. You can adjust the number of charts in the code.
https://glitch.com/edit/#!/neighborly-cuboid-card
Since your code is using React, could you check that you are only creating the expected number of charts, and that you are removing charts when they are no longer needed with the remove method on the ChartApi

@SlicedSilver - I would like to thank you for your support.
I have Observed in the above example, each chart have only one series.   But our case is multiple charts and each charts has multple series  (may the series has any type(line, area, grouped bar charts)).
Please provide example for tooltip functionality for multi charts multi series data.
As per the example I have tried to create multi series but am not able to get expected solution.
https://github.com/tradingview/lightweight-charts/issues/1589 this ticket also I have raised for the inclusion of grouped bar charts in multi series charts. For this case only. Unfortunately unable to create grouped Bar charts which has positive and negative bars.
We have used TradingView and Light weight charts in our projects, we were already integrated couple of screens. But we got stucked multi charts with multi series where the series has grouped Bar charts. It is showstopper for a long time. So, requesting you to please provide all solutions at one place if posible. Thanks for an advance.
Here is  my trail
https://codesandbox.io/p/sandbox/frosty-star-h2jtj3?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clw50tevc0006356lsacswzy0%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clw50tevc0002356ly4ebxte2%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clw50tevc0003356l7576h87s%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clw50tevc0005356lyxheupt7%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clw50tevc0002356ly4ebxte2%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clw50tevc0001356lg9dm72aj%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Findex.html%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clw50tevc0002356ly4ebxte2%2522%252C%2522activeTabId%2522%253A%2522clw50tevc0001356lg9dm72aj%2522%257D%252C%2522clw50tevc0005356lyxheupt7%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clw50tevc0004356lkzasyawb%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A0%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522clw50tevc0005356lyxheupt7%2522%252C%2522activeTabId%2522%253A%2522clw50tevc0004356lkzasyawb%2522%257D%252C%2522clw50tevc0003356l7576h87s%2522%253A%257B%2522tabs%2522%253A%255B%255D%252C%2522id%2522%253A%2522clw50tevc0003356l7576h87s%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Afalse%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D

I have Observed in the above example, each chart have only one series. But our case is multiple charts and each charts has multple series (may the series has any type(line, area, grouped bar charts)).

Having multiple series on the chart shouldn't make any difference. I've updated the example to have a second series on each chart.

Please provide example for tooltip functionality for multi charts multi series data.

We have some tutorials on adding tooltips. Besides what is shown in the documentation, we wouldn't create an custom example for just a single request.

https://github.com/tradingview/lightweight-charts/issues/1589 this ticket also I have raised for the inclusion of grouped bar charts in multi series charts. For this case only. Unfortunately unable to create grouped Bar charts which has positive and negative bars.

If the plugin doesn't support negative bars then I would suggest that you take the source code of the plugin as a starting point and develop an updated plugin. The example plugin is only an example instead of a full-featured official plugin.

So, requesting you to please provide all solutions at one place if posible.

We don't build example implementations as that would be consultation work and we don't offer paid services. For this open source project, we are happy to assist with bug reports and general queries, and consider feature requests but not to develop full solutions or perform code review on large files (beyond minimal bug reproductions).

@SlicedSilver I've tried syncing two charts with 'Set crosshair position,' but it seems that it's not synchronizing properly.
Screen.Recording.2023-12-14.at.10.32.03.AM.mp4

Hello @dc-thanh
Did you managed this out?  Have a similar issue with multiple charts with different timestamps on each, which leads to different timeScale section width.


HLS Streaming?

Does it works with HLS adaptive bitrate streaming?
Got a m3u8 with its .ts file chunks to play on android. Thanks

This is only a demo for MediaCodec API. I recommend the ExoPlayer from Google, which I use in my own projects.


Add bootstrap support for SLES

What this PR does / why we need it:
Add bootstrap script for SLES
Which issue(s) this PR fixes (optional, in fixes #<issue number> format, will close the issue(s) when PR gets merged):
Fixes #
Special notes for your reviewer:
Optional Release Note:

NONE

xref https://github.com/kubermatic/operating-system-manager/issues/80

/retest


fix can_commit to use get_weights_set_rate_limit

Description
Previously, the can_commit function checked to see if a whole commit_reveal_weights_interval has passed before being allowed to commit weights again. Instead, we should keep this logic for when we can reveal_weights (as is intended), but the neuron should be able to commit weights multiple times within a single commit_reveal_weights_interval according to the weights_rate_limit hyperparameter. If not, the weights_rate_limit is essentually useless because the can_commit function overrides the weights_rate_limit.
Furthermore, it introduces an edge case where if commit_reveal_weights_interval if > activity_cutoff, all weights go away and the network stalls because neurons are now setting weights > once per activity_cutoff. This PR fixes this too.
Type of Change

[X] Bug fix (non-breaking change which fixes an issue)
[ ] New feature (non-breaking change which adds functionality)
[ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
[ ] Documentation update
[ ] Other (please describe):

Checklist

[x] I have performed a self-review of my own code
[ ] I have commented my code, particularly in hard-to-understand areas
[ ] I have run cargo fmt and cargo clippy to ensure my code is formatted and linted correctly
[ ] I have made corresponding changes to the documentation
[ ] My changes generate no new warnings
[ ] I have added tests that prove my fix is effective or that my feature works
[ ] New and existing unit tests pass locally with my changes
[ ] Any dependent changes have been merged and published in downstream modules

Screenshots (if applicable)
Please include any relevant screenshots or GIFs that demonstrate the changes made.
Additional Notes
Please provide any additional information or context that may be helpful for reviewers.

forked from wrong base branch


Split a csv file using line count

Problem Statement
Split a CSV,Text file using line count
Steps

[ ] Accept file name as input
[ ]  Accept no of lines to split
[ ]  create a folder and and save  files

I will do this.

@phileinSophos merged


Design suggestion: remove forward from list of methods to override

🚀 Feature (?)
This is more a philosophical design suggestion than a feature request.
I think that the presentation of LightningModule as a torch.Module-plus-features encourages early experiment designs that don't refactor nicely as the projects using it grow.
I also think that calling self.forward directly is a torch anti-pattern, and should not be encouraged.
I'd like the official docs to suggest using a self.my_model = MyModel(...) in __init__ and y = self.my_model(x) in training_step etc.
Motivation
I think that most non-research uses of lightning are going to require that the environment the model is trained in be separable from the model itself. This is most obvious when considering the infrastructure needed to load training data vs. production inference data; you're not going to want to drag along all of the libraries needed to connect to a database, decompress data, etc. in the production environment.
To do so, I'd need to be able to from some.other.package import MyModel and then self.my_model = MyModel(...) in __init__. As long as some.other.package doesn't have extra dependencies, I can ship my production model and weights to production without needing everything else that lightning, etc. depends on.
By suggesting that users have the lightning subclass be the model, the set of packages that need to be present in production goes up quite a bit (speaking from experience, the pip version management becomes painful).
Another thing that this makes unclear, then, is what is actually happening when training_step gets called. The suggestion "Normally you'd call self.forward() from your training_step() method." implies that self.training_step is happening inside of a self.__call__ since torch.nn.Module.forward isn't supposed to be called directly (since it's __call__ that handles hooks, etc.), but that doesn't actually seem to be the case. Unless I'm missing something, this really feels like misuse of the torch API.
By making it clear that your LightningModule subclass should have an instance of your model as an attribute, not be the model, all of the above gets cleared up quite a bit.
Pitch
I think it's a lot cleaner and clearer to say "Normally you'd call y = self.my_model(x) from your training_step() method." and remove any suggestion of overriding self.forward() from the documentation (and I'd in fact make the default implementation of forward raise a YouAreDoingItWrongException).
As I said earlier, I think that projects that mix training and model code in the same class are going to have a difficult time refactoring things later on, and I think that the perceived simplicity early on is a mastery trap. Anyone familiar with PyTorch isn't going to have a problem defining a separate model class.
Alternatives
Note that I don't think there's anything preventing me from implementing models the way I think is proper right now, but I'm currently doing an investigation into if we can use lightning for more projects in the organization, and I'd really rather not having to try and educate users to ignore the docs and do it the self.my_model() way instead.
At the very least, changing the documentation to say "Normally you'd call y = self(x) from your training_step() method." makes sure that hooks, etc. get called as expected.
Additional context
Now, I will fully admit that I haven't dug into lightning a ton yet, so it's possible that I'm missing something that will change my understanding/perception of things. If that's the case, I think it should be articulated more clearly.
Thanks for reading.

I recently started using Lightning for a project I have been working on and I needed to import the model from a seperate module like you stated @elistevens.
In my Lightning init i just instantiate my external model and override the forward to return mymode(x). This works fine, however, I agree that it might be better to have the model as an attribute as opposed to Lightning being the model.

This would also help with more complicated research projects that involve multiple models (autoencoders or GANs, for example) and make things a lot more flexible and pythonic, "pytorchic."

@darwinkim I agree with @elistevens that it will be useful to be able to "extract" a more lightweight to ship to production. However, can you provide an example as to how this would help with more complicated research projects that involve multiple models?
There's a GAN example here which shows how you can cleanly incorporate multiple models.

I wonder if there's a way we could expose a jit_export functionality which JIT compiles the model and extracts out only that which is necessary for serving inference. The LightningModel would contain everything needed for training and it could export a minimal model for inference as a post-training artifact.

@jeremyjordan
https://arxiv.org/abs/1703.00848
Involves training six networks as two autoencoders and two GANs on two datasets

just a note: PR #1211 promotes the use of self(...) instead of self.forward in examples and docs.

@williamFalcon @PyTorchLightning/core-contributors ^^

@williamFalcon asked me to revisit this, so I'm adding some more thoughts. PR #1211 fixed the issue of suggesting that users call .forward() directly, but there's another layer to what I'm trying to suggest.
Essentially, I'm wanting to clearly and cleanly separate concerns, and have that clean separation be suggested by the documentation. From a OOP perspective, the documentation suggests that the training loop object and the model object be the same object and that mixes two separate concepts.
Put another way, if you were going to be using a stock model from torchvision, you wouldn't have class MyModel(pl.LightningModule, ResNet): you'd have class MyModel(pl.LightningModule): def __init__(self): self.model = ResNet(). The training loop and the model would be separate python objects, and you could do things with the model such that you'd never know it was trained with lightning.  For example, save/load the weights, or export it to onnx, etc.
How it's suggested now, it becomes much harder to pull out my model and use it in some other context (like a different training loop). I typically try to avoid libraries with that kind of lock-in.

Thanks for adding more details!
I use lightning a lot the way you describe. What gives you the impression that you can’t use it this way?
Is there a better way to show this in the docs or examples?
Take a look at all the bolts models. Most models in bolts have that pattern.
https://pytorch-lightning-bolts.readthedocs.io/en/latest/self_supervised_models.html#byol

Ok, added the following to the docs to clarify this particular use case of a lightning module.

all items here were addressed a while ago. we can close

forward is optional to implement
our docs favor self() instead of .forward()
if someone calls an unimplemented forward manually, it  will call into super().forward


Minify .out/prod/extension-prod.js

ms-vsliveshare.vsliveshare-1.0.5776
Hi, I noticed that this extension is shipping a 12MB js file which appears to be unminified:

Could you run this through a minifier to reduce the extension's download and on disk sizes?

Hi @mjbvz, good observation. That is the legacy agent extension. There is work in progress to remove all of this code. It will dramatically reduce the footprint of the Live Share vsix. The agentless extension is minified. Stay tuned!

Thanks @mjbvz : we've removed this legacy file in the latest release (1.0.5783). The remaining browser and agentless extension files are minified.


Add an alternative name for USVI lookup

Looking up for an address in Virgin Islands US using Google Maps API responds with U.S. Virgin Islands as the country name:
{
  "long_name" : "U.S. Virgin Islands",
  "short_name" : "VI",
  "types" : [ "country", "political" ]
},

This country name isn't specified as an unofficial name so lookup fails:
[1] pry(main)> IS
O3166::Country.find_country_by_any_name("U.S. Virgin Islands")
=> nil

Thanks for the PR!