Skip to content

Commit f11ed7e

Browse files
authored
Merge pull request tradingview#1977 from tradingview/feature/include-top-layer-to-screenshot
add ability to include top layer to screenshot
2 parents 00af85d + 53f9fb4 commit f11ed7e

File tree

6 files changed

+54
-12
lines changed

6 files changed

+54
-12
lines changed

src/api/chart-api.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { warn } from '../helpers/logger';
77
import { clone, DeepPartial, isBoolean, merge } from '../helpers/strict-type-checks';
88

99
import { ChartOptionsImpl, ChartOptionsInternal } from '../model/chart-model';
10+
import { CrosshairMode } from '../model/crosshair';
1011
import { DataUpdatesConsumer, isFulfilledData, SeriesDataItemTypeMap, WhitespaceData } from '../model/data-consumer';
1112
import { DataLayer, DataUpdateResponse, SeriesChanges } from '../model/data-layer';
1213
import { CustomData, ICustomSeriesPaneView } from '../model/icustom-series';
@@ -285,8 +286,29 @@ export class ChartApi<HorzScaleItem> implements IChartApiBase<HorzScaleItem>, Da
285286
return this._chartWidget.options() as Readonly<ChartOptionsImpl<HorzScaleItem>>;
286287
}
287288

288-
public takeScreenshot(): HTMLCanvasElement {
289-
return this._chartWidget.takeScreenshot();
289+
public takeScreenshot(addTopLayer: boolean = false, includeCrosshair: boolean = false): HTMLCanvasElement {
290+
let crosshairMode: CrosshairMode | undefined;
291+
let screenshotCanvas: HTMLCanvasElement;
292+
try {
293+
if (!includeCrosshair) {
294+
crosshairMode = this._chartWidget.model().options().crosshair.mode;
295+
this._chartWidget.applyOptions({
296+
crosshair: {
297+
mode: CrosshairMode.Hidden,
298+
},
299+
});
300+
}
301+
screenshotCanvas = this._chartWidget.takeScreenshot(addTopLayer);
302+
} finally {
303+
if (!includeCrosshair && crosshairMode !== undefined) {
304+
this._chartWidget.model().applyOptions({
305+
crosshair: {
306+
mode: crosshairMode,
307+
},
308+
});
309+
}
310+
}
311+
return screenshotCanvas;
290312
}
291313

292314
public addPane(preserveEmptyPane: boolean = false): IPaneApi<HorzScaleItem> {

src/api/ichart-api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,11 @@ export interface IChartApiBase<HorzScaleItem = Time> {
272272
/**
273273
* Make a screenshot of the chart with all the elements excluding crosshair.
274274
*
275+
* @param addTopLayer - if true, the top layer and primitives will be included in the screenshot (default: false)
276+
* @param includeCrosshair - works only if addTopLayer is enabled. If true, the crosshair will be included in the screenshot (default: false)
275277
* @returns A canvas with the chart drawn on. Any `Canvas` methods like `toDataURL()` or `toBlob()` can be used to serialize the result.
276278
*/
277-
takeScreenshot(): HTMLCanvasElement;
279+
takeScreenshot(addTopLayer?: boolean, includeCrosshair?: boolean): HTMLCanvasElement;
278280

279281
/**
280282
* Add a pane to the chart

src/gui/chart-widget.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ export class ChartWidget<HorzScaleItem> implements IDestroyable, IChartWidgetBas
262262
return this._crosshairMoved;
263263
}
264264

265-
public takeScreenshot(): HTMLCanvasElement {
265+
public takeScreenshot(addTopLayer: boolean = false): HTMLCanvasElement {
266266
if (this._invalidateMask !== null) {
267267
this._drawImpl(this._invalidateMask, performance.now());
268268
this._invalidateMask = null;
@@ -274,7 +274,7 @@ export class ChartWidget<HorzScaleItem> implements IDestroyable, IChartWidgetBas
274274
screenshotCanvas.height = screeshotBitmapSize.height;
275275

276276
const ctx = ensureNotNull(screenshotCanvas.getContext('2d'));
277-
this._traverseLayout(ctx);
277+
this._traverseLayout(ctx, addTopLayer);
278278

279279
return screenshotCanvas;
280280
}
@@ -357,9 +357,10 @@ export class ChartWidget<HorzScaleItem> implements IDestroyable, IChartWidgetBas
357357
* draws the screenshot (if rendering context is passed) and returns the screenshot bitmap size
358358
*
359359
* @param ctx - if passed, used to draw the screenshot of widget
360+
* @param addTopLayer - if true, the top layer with crosshair and primitives will be drawn
360361
* @returns screenshot bitmap size
361362
*/
362-
private _traverseLayout(ctx: CanvasRenderingContext2D | null): Size {
363+
private _traverseLayout(ctx: CanvasRenderingContext2D | null, addTopLayer?: boolean): Size {
363364
let totalWidth = 0;
364365
let totalHeight = 0;
365366

@@ -372,7 +373,7 @@ export class ChartWidget<HorzScaleItem> implements IDestroyable, IChartWidgetBas
372373
const priceAxisWidget = ensureNotNull(position === 'left' ? paneWidget.leftPriceAxisWidget() : paneWidget.rightPriceAxisWidget());
373374
const bitmapSize = priceAxisWidget.getBitmapSize();
374375
if (ctx !== null) {
375-
priceAxisWidget.drawBitmap(ctx, targetX, targetY);
376+
priceAxisWidget.drawBitmap(ctx, targetX, targetY, addTopLayer);
376377
}
377378
targetY += bitmapSize.height;
378379
if (paneIndex < this._paneWidgets.length - 1) {
@@ -396,7 +397,7 @@ export class ChartWidget<HorzScaleItem> implements IDestroyable, IChartWidgetBas
396397
const paneWidget = this._paneWidgets[paneIndex];
397398
const bitmapSize = paneWidget.getBitmapSize();
398399
if (ctx !== null) {
399-
paneWidget.drawBitmap(ctx, totalWidth, totalHeight);
400+
paneWidget.drawBitmap(ctx, totalWidth, totalHeight, addTopLayer);
400401
}
401402
totalHeight += bitmapSize.height;
402403
if (paneIndex < this._paneWidgets.length - 1) {
@@ -434,7 +435,7 @@ export class ChartWidget<HorzScaleItem> implements IDestroyable, IChartWidgetBas
434435
targetX = ensureNotNull(firstPane.leftPriceAxisWidget()).getBitmapSize().width;
435436
}
436437

437-
this._timeAxisWidget.drawBitmap(ctx, targetX, totalHeight);
438+
this._timeAxisWidget.drawBitmap(ctx, targetX, totalHeight, addTopLayer);
438439
targetX += timeAxisBitmapSize.width;
439440

440441
if (this._isRightAxisVisible()) {

src/gui/pane-widget.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,17 @@ export class PaneWidget implements IDestroyable, MouseEventHandlers {
453453
return this._canvasBinding.bitmapSize;
454454
}
455455

456-
public drawBitmap(ctx: CanvasRenderingContext2D, x: number, y: number): void {
456+
public drawBitmap(ctx: CanvasRenderingContext2D, x: number, y: number, addTopLayer?: boolean): void {
457457
const bitmapSize = this.getBitmapSize();
458458
if (bitmapSize.width > 0 && bitmapSize.height > 0) {
459459
ctx.drawImage(this._canvasBinding.canvasElement, x, y);
460+
461+
if (addTopLayer) {
462+
const topLayer = this._topCanvasBinding.canvasElement;
463+
if (ctx !== null) {
464+
ctx.drawImage(topLayer, x, y);
465+
}
466+
}
460467
}
461468
}
462469

src/gui/price-axis-widget.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,10 +392,15 @@ export class PriceAxisWidget implements IDestroyable {
392392
return this._canvasBinding.bitmapSize;
393393
}
394394

395-
public drawBitmap(ctx: CanvasRenderingContext2D, x: number, y: number): void {
395+
public drawBitmap(ctx: CanvasRenderingContext2D, x: number, y: number, addTopLayer?: boolean): void {
396396
const bitmapSize = this.getBitmapSize();
397397
if (bitmapSize.width > 0 && bitmapSize.height > 0) {
398398
ctx.drawImage(this._canvasBinding.canvasElement, x, y);
399+
400+
if (addTopLayer) {
401+
const topLayer = this._topCanvasBinding.canvasElement;
402+
ctx.drawImage(topLayer, x, y);
403+
}
399404
}
400405
}
401406

src/gui/time-axis-widget.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,15 @@ export class TimeAxisWidget<HorzScaleItem> implements MouseEventHandlers, IDestr
287287
return this._canvasBinding.bitmapSize;
288288
}
289289

290-
public drawBitmap(ctx: CanvasRenderingContext2D, x: number, y: number): void {
290+
public drawBitmap(ctx: CanvasRenderingContext2D, x: number, y: number, addTopLayer?: boolean): void {
291291
const bitmapSize = this.getBitmapSize();
292292
if (bitmapSize.width > 0 && bitmapSize.height > 0) {
293293
ctx.drawImage(this._canvasBinding.canvasElement, x, y);
294+
295+
if (addTopLayer) {
296+
const topLayer = this._topCanvasBinding.canvasElement;
297+
ctx.drawImage(topLayer, x, y);
298+
}
294299
}
295300
}
296301

0 commit comments

Comments
 (0)