From 58c45797dd4a9ebeac09c16a761aecdb310fcc9c Mon Sep 17 00:00:00 2001 From: Mark Silverwood Date: Mon, 19 Jun 2023 18:52:51 +0100 Subject: [PATCH 1/3] added pixel rendering guide for plugin developers --- .../pixel-perfect-rendering/_category_.yml | 2 + .../plugins/pixel-perfect-rendering/index.md | 101 +++++++ .../widths/_category_.yml | 2 + .../widths/candlestick.md | 81 ++++++ .../pixel-perfect-rendering/widths/columns.md | 269 ++++++++++++++++++ .../widths/crosshair.md | 54 ++++ .../widths/full-bar-width.md | 63 ++++ 7 files changed, 572 insertions(+) create mode 100644 website/docs/plugins/pixel-perfect-rendering/_category_.yml create mode 100644 website/docs/plugins/pixel-perfect-rendering/index.md create mode 100644 website/docs/plugins/pixel-perfect-rendering/widths/_category_.yml create mode 100644 website/docs/plugins/pixel-perfect-rendering/widths/candlestick.md create mode 100644 website/docs/plugins/pixel-perfect-rendering/widths/columns.md create mode 100644 website/docs/plugins/pixel-perfect-rendering/widths/crosshair.md create mode 100644 website/docs/plugins/pixel-perfect-rendering/widths/full-bar-width.md diff --git a/website/docs/plugins/pixel-perfect-rendering/_category_.yml b/website/docs/plugins/pixel-perfect-rendering/_category_.yml new file mode 100644 index 0000000000..f5f99ef932 --- /dev/null +++ b/website/docs/plugins/pixel-perfect-rendering/_category_.yml @@ -0,0 +1,2 @@ +label: "Pixel Perfect Rendering" +position: 5 diff --git a/website/docs/plugins/pixel-perfect-rendering/index.md b/website/docs/plugins/pixel-perfect-rendering/index.md new file mode 100644 index 0000000000..d1017ac2de --- /dev/null +++ b/website/docs/plugins/pixel-perfect-rendering/index.md @@ -0,0 +1,101 @@ +--- +sidebar_position: 0 +sidebar_label: Pixel Perfect Rendering +pagination_title: Pixel Perfect Rendering +title: Best Practices for Pixel Perfect Rendering in Canvas Drawings +description: Best Practices for Pixel Perfect Rendering in Canvas Drawings when creating plugins for the Lightweight Charts +keywords: + - plugins + - extensions + - rendering + - canvas + - bitmap + - media + - pixels +pagination_prev: null +--- + +To achieve crisp pixel perfect rendering for your plugins, it is recommended that the canvas drawings are created using bitmap coordinates. The difference between media and bitmap coordinate spaces is discussed on the [Canvas Rendering Target](../canvas-rendering-target.md) page. **Essentially, all drawing actions should use integer positions and dimensions when on the bitmap coordinate space.** + +To ensure consistency between your plugins and the library's built-in logic for rendering points on the chart, use of the following calculation functions. + +:::info + +Variable names containing `media` refer to positions / dimensions specified using the media coordinate space (such as the x and y coordinates provided by the library to the renderers), and names containing `bitmap` refer to positions / dimensions on the bitmap coordinate space (actual device screen pixels). + +::: + +## Centered Shapes + +If you need to draw a shape which is centred on a position (for example a price or x coordinate) and has a desired width then you could use the `positionsLine` function presented below. This can be used for drawing a horizontal line at a specific price, or a vertical line aligned with the centre of series point. + +```typescript +interface BitmapPositionLength { + /** coordinate for use with a bitmap rendering scope */ + position: number; + /** length for use with a bitmap rendering scope */ + length: number; +} + +function centreOffset(lineBitmapWidth: number): number { + return Math.floor(lineBitmapWidth * 0.5); +} + +/** + * Calculates the bitmap position for an item with a desired length (height or width), and centred according to + * an position coordinate defined in media sizing. + * @param positionMedia - position coordinate for the bar (in media coordinates) + * @param pixelRatio - pixel ratio. Either horizontal for x positions, or vertical for y positions + * @param desiredWidthMedia - desired width (in media coordinates) + * @returns Position of of the start point and length dimension. + */ +export function positionsLine( + positionMedia: number, + pixelRatio: number, + desiredWidthMedia: number = 1, + widthIsBitmap?: boolean +): BitmapPositionLength { + const scaledPosition = Math.round(pixelRatio * positionMedia); + const lineBitmapWidth = widthIsBitmap + ? desiredWidthMedia + : Math.round(desiredWidthMedia * pixelRatio); + const offset = centreOffset(lineBitmapWidth); + const position = scaledPosition - offset; + return { position, length: lineBitmapWidth }; +} +``` + +## Dual Point Shapes + +If you need to draw a shape between two coordinates (for example, y coordinates for a high and low price) then you can use the `positionsBox` function as presented below. + +```typescript +/** + * Determines the bitmap position and length for a dimension of a shape to be drawn. + * @param position1Media - media coordinate for the first point + * @param position2Media - media coordinate for the second point + * @param pixelRatio - pixel ratio for the corresponding axis (vertical or horizontal) + * @returns Position of of the start point and length dimension. + */ +export function positionsBox( + position1Media: number, + position2Media: number, + pixelRatio: number +): BitmapPositionLength { + const scaledPosition1 = Math.round(pixelRatio * position1Media); + const scaledPosition2 = Math.round(pixelRatio * position2Media); + return { + position: Math.min(scaledPosition1, scaledPosition2), + length: Math.abs(scaledPosition2 - scaledPosition1) + 1, + }; +} +``` + +## Defaults Widths + +Please refer to the following pages for functions defining the default widths of shapes drawn by the library: + +- [Crosshair and Grid Lines](./widths/crosshair.md) +- [Candlesticks](./widths/candlestick.md) +- [Columns (Histogram)](./widths/columns.md) +- [Full Bar Width](./widths/full-bar-width.md) diff --git a/website/docs/plugins/pixel-perfect-rendering/widths/_category_.yml b/website/docs/plugins/pixel-perfect-rendering/widths/_category_.yml new file mode 100644 index 0000000000..9ccc1a42ca --- /dev/null +++ b/website/docs/plugins/pixel-perfect-rendering/widths/_category_.yml @@ -0,0 +1,2 @@ +label: "Default Widths" +position: 0 diff --git a/website/docs/plugins/pixel-perfect-rendering/widths/candlestick.md b/website/docs/plugins/pixel-perfect-rendering/widths/candlestick.md new file mode 100644 index 0000000000..43bd78c391 --- /dev/null +++ b/website/docs/plugins/pixel-perfect-rendering/widths/candlestick.md @@ -0,0 +1,81 @@ +--- +sidebar_position: 0 +sidebar_label: Candlesticks +pagination_title: Candlestick Widths +title: Candlestick Width Calculations +description: Describes the calculation for candlestick body widths +keywords: + - plugins + - extensions + - rendering + - canvas + - bitmap + - media + - pixels + - candlestick + - width +--- + +:::tip + +It is recommend that you first read the [Pixel Perfect Rendering](../index.md) page. + +::: + +The following functions can be used to get the calculated width that the library would use for a candlestick at a specific bar spacing and device pixel ratio. + +Below a bar spacing of 4, the library will attempt to use as large a width as possible without the possibility of overlapping, whilst above 4 then the width will start to trend towards an 80% width of the available space. + +:::warning + +It is expected that candles can overlap slightly at smaller bar spacings (more pronounced on lower resolution devices). This produces a more readable chart. If you need to ensure that bars can never overlap then rather use the widths for [Columns](./columns.md) or the [full bar width](./full-bar-width.md) calculation. + +::: + +```typescript +function optimalCandlestickWidth( + barSpacing: number, + pixelRatio: number +): number { + const barSpacingSpecialCaseFrom = 2.5; + const barSpacingSpecialCaseTo = 4; + const barSpacingSpecialCaseCoeff = 3; + if (barSpacing >= barSpacingSpecialCaseFrom && barSpacing <= barSpacingSpecialCaseTo) { + return Math.floor(barSpacingSpecialCaseCoeff * pixelRatio); + } + // coeff should be 1 on small barspacing and go to 0.8 while bar spacing grows + const barSpacingReducingCoeff = 0.2; + const coeff = + 1 - + (barSpacingReducingCoeff * + Math.atan( + Math.max(barSpacingSpecialCaseTo, barSpacing) - barSpacingSpecialCaseTo + )) / + (Math.PI * 0.5); + const res = Math.floor(barSpacing * coeff * pixelRatio); + const scaledBarSpacing = Math.floor(barSpacing * pixelRatio); + const optimal = Math.min(res, scaledBarSpacing); + return Math.max(Math.floor(pixelRatio), optimal); +} + +/** + * Calculates the candlestick width that the library would use for the current + * bar spacing. + * @param barSpacing bar spacing in media coordinates + * @param horizontalPixelRatio - horizontal pixel ratio + * @returns The width (in bitmap coordinates) that the chart would use to draw a candle body + */ +export function candlestickWidth( + barSpacing: number, + horizontalPixelRatio: number +): number { + let width = optimalCandlestickWidth(barSpacing, horizontalPixelRatio); + if (width >= 2) { + const wickWidth = Math.floor(horizontalPixelRatio); + if (wickWidth % 2 !== width % 2) { + width--; + } + } + return width; +} +``` diff --git a/website/docs/plugins/pixel-perfect-rendering/widths/columns.md b/website/docs/plugins/pixel-perfect-rendering/widths/columns.md new file mode 100644 index 0000000000..c2384f27de --- /dev/null +++ b/website/docs/plugins/pixel-perfect-rendering/widths/columns.md @@ -0,0 +1,269 @@ +--- +sidebar_position: 0 +sidebar_label: Columns +pagination_title: Histogram Column Widths +title: Histogram Column Width Calculations +description: Describes the calculation for histogram column widths +keywords: + - plugins + - extensions + - rendering + - canvas + - bitmap + - media + - pixels + - histogram + - column + - width +--- + +:::tip + +It is recommend that you first read the [Pixel Perfect Rendering](../index.md) page. + +::: + +The following functions can be used to get the calculated width that the library would use for a histogram column at a specific bar spacing and device pixel ratio. + +You can use the `calculateColumnPositionsInPlace` function instead of the `calculateColumnPositions` function to perform the calculation on an existing array of items without needing to create additional arrays (which is more efficient). It is recommended that you memoize the majority of the calculations below to improve the rendering performance. + +```typescript +const alignToMinimalWidthLimit = 4; +const showSpacingMinimalBarWidth = 1; + +/** + * Spacing gap between columns. + * @param barSpacingMedia - spacing between bars (media coordinate) + * @param horizontalPixelRatio - horizontal pixel ratio + * @returns Spacing gap between columns (in Bitmap coordinates) + */ +function columnSpacing(barSpacingMedia: number, horizontalPixelRatio: number) { + return Math.ceil(barSpacingMedia * horizontalPixelRatio) <= + showSpacingMinimalBarWidth + ? 0 + : Math.max(1, Math.floor(horizontalPixelRatio)); +} + +/** + * Desired width for columns. This may not be the final width because + * it may be adjusted later to ensure all columns on screen have a + * consistent width and gap. + * @param barSpacingMedia - spacing between bars (media coordinate) + * @param horizontalPixelRatio - horizontal pixel ratio + * @param spacing - Spacing gap between columns (in Bitmap coordinates). (optional, provide if you have already calculated it) + * @returns Desired width for column bars (in Bitmap coordinates) + */ +function desiredColumnWidth( + barSpacingMedia: number, + horizontalPixelRatio: number, + spacing?: number +) { + return ( + Math.round(barSpacingMedia * horizontalPixelRatio) - + (spacing ?? columnSpacing(barSpacingMedia, horizontalPixelRatio)) + ); +} + +interface ColumnCommon { + /** Spacing gap between columns */ + spacing: number; + /** Shift columns left by one pixel */ + shiftLeft: boolean; + /** Half width of a column */ + columnHalfWidthBitmap: number; + /** horizontal pixel ratio */ + horizontalPixelRatio: number; +} + +/** + * Calculated values which are common to all the columns on the screen, and + * are required to calculate the individual positions. + * @param barSpacingMedia - spacing between bars (media coordinate) + * @param horizontalPixelRatio - horizontal pixel ratio + * @returns calculated values for subsequent column calculations + */ +function columnCommon( + barSpacingMedia: number, + horizontalPixelRatio: number +): ColumnCommon { + const spacing = columnSpacing(barSpacingMedia, horizontalPixelRatio); + const columnWidthBitmap = desiredColumnWidth( + barSpacingMedia, + horizontalPixelRatio, + spacing + ); + const shiftLeft = columnWidthBitmap % 2 === 0; + const columnHalfWidthBitmap = (columnWidthBitmap - (shiftLeft ? 0 : 1)) / 2; + return { + spacing, + shiftLeft, + columnHalfWidthBitmap, + horizontalPixelRatio, + }; +} + +interface ColumnPosition { + left: number; + right: number; + shiftLeft: boolean; +} + +/** + * Calculate the position for a column. These values can be later adjusted + * by a second pass which corrects widths, and shifts columns. + * @param xMedia - column x position (center) in media coordinates + * @param columnData - precalculated common values (returned by `columnCommon`) + * @param previousPosition - result from this function for the previous bar. + * @returns initial column position + */ +function calculateColumnPosition( + xMedia: number, + columnData: ColumnCommon, + previousPosition: ColumnPosition | undefined +): ColumnPosition { + const xBitmapUnRounded = xMedia * columnData.horizontalPixelRatio; + const xBitmap = Math.round(xBitmapUnRounded); + const xPositions: ColumnPosition = { + left: xBitmap - columnData.columnHalfWidthBitmap, + right: + xBitmap + + columnData.columnHalfWidthBitmap - + (columnData.shiftLeft ? 1 : 0), + shiftLeft: xBitmap > xBitmapUnRounded, + }; + const expectedAlignmentShift = columnData.spacing + 1; + if (previousPosition) { + if (xPositions.left - previousPosition.right !== expectedAlignmentShift) { + // need to adjust alignment + if (previousPosition.shiftLeft) { + previousPosition.right = xPositions.left - expectedAlignmentShift; + } else { + xPositions.left = previousPosition.right + expectedAlignmentShift; + } + } + } + return xPositions; +} + +function fixPositionsAndReturnSmallestWidth( + positions: ColumnPosition[], + initialMinWidth: number +): number { + return positions.reduce((smallest: number, position: ColumnPosition) => { + if (position.right < position.left) { + position.right = position.left; + } + const width = position.right - position.left + 1; + return Math.min(smallest, width); + }, initialMinWidth); +} + +function fixAlignmentForNarrowColumns( + positions: ColumnPosition[], + minColumnWidth: number +) { + return positions.map((position: ColumnPosition) => { + const width = position.right - position.left + 1; + if (width <= minColumnWidth) return position; + if (position.shiftLeft) { + position.right -= 1; + } else { + position.left += 1; + } + return position; + }); +} + +/** + * Calculates the column positions and widths for the x positions. + * This function creates a new array. You may get faster performance using the + * `calculateColumnPositionsInPlace` function instead + * @param xMediaPositions - x positions for the bars in media coordinates + * @param barSpacingMedia - spacing between bars in media coordinates + * @param horizontalPixelRatio - horizontal pixel ratio + * @returns Positions for the columns + */ +export function calculateColumnPositions( + xMediaPositions: number[], + barSpacingMedia: number, + horizontalPixelRatio: number +): ColumnPosition[] { + const common = columnCommon(barSpacingMedia, horizontalPixelRatio); + const positions = new Array(xMediaPositions.length); + let previous: ColumnPosition | undefined = undefined; + for (let i = 0; i < xMediaPositions.length; i++) { + positions[i] = calculateColumnPosition( + xMediaPositions[i], + common, + previous + ); + previous = positions[i]; + } + const initialMinWidth = Math.ceil(barSpacingMedia * horizontalPixelRatio); + const minColumnWidth = fixPositionsAndReturnSmallestWidth( + positions, + initialMinWidth + ); + if (common.spacing > 0 && minColumnWidth < alignToMinimalWidthLimit) { + return fixAlignmentForNarrowColumns(positions, minColumnWidth); + } + return positions; +} + +export interface ColumnPositionItem { + x: number; + column?: ColumnPosition; +} + +/** + * Calculates the column positions and widths for bars using the existing the + * array of items. + * @param items - bar items which include an `x` property, and will be mutated to contain a column property + * @param barSpacingMedia - bar spacing in media coordinates + * @param horizontalPixelRatio - horizontal pixel ratio + * @param startIndex - start index for visible bars within the items array + * @param endIndex - end index for visible bars within the items array + */ +export function calculateColumnPositionsInPlace( + items: ColumnPositionItem[], + barSpacingMedia: number, + horizontalPixelRatio: number, + startIndex: number, + endIndex: number +): void { + const common = columnCommon(barSpacingMedia, horizontalPixelRatio); + let previous: ColumnPosition | undefined = undefined; + for (let i = startIndex; i < Math.min(endIndex, items.length); i++) { + items[i].column = calculateColumnPosition(items[i].x, common, previous); + previous = items[i].column; + } + const minColumnWidth = (items as ColumnPositionItem[]).reduce( + (smallest: number, item: ColumnPositionItem, index: number) => { + if (!item.column || index < startIndex || index > endIndex) + return smallest; + if (item.column.right < item.column.left) { + item.column.right = item.column.left; + } + const width = item.column.right - item.column.left + 1; + return Math.min(smallest, width); + }, + Math.ceil(barSpacingMedia * horizontalPixelRatio) + ); + if (common.spacing > 0 && minColumnWidth < alignToMinimalWidthLimit) { + (items as ColumnPositionItem[]).forEach( + (item: ColumnPositionItem, index: number) => { + if (!item.column || index < startIndex || index > endIndex) return; + const width = item.column.right - item.column.left + 1; + if (width <= minColumnWidth) return item; + if (item.column.shiftLeft) { + item.column.right -= 1; + } else { + item.column.left += 1; + } + return item.column; + } + ); + } +} + +``` diff --git a/website/docs/plugins/pixel-perfect-rendering/widths/crosshair.md b/website/docs/plugins/pixel-perfect-rendering/widths/crosshair.md new file mode 100644 index 0000000000..1099368268 --- /dev/null +++ b/website/docs/plugins/pixel-perfect-rendering/widths/crosshair.md @@ -0,0 +1,54 @@ +--- +sidebar_position: 0 +sidebar_label: Crosshair +pagination_title: Crosshair Widths +title: Crosshair and Grid Line Width Calculations +description: Describes the calculation for the crosshair line and grid line widths +keywords: + - plugins + - extensions + - rendering + - canvas + - bitmap + - media + - pixels + - crosshair + - grid + - line + - width +--- + +:::tip + +It is recommend that you first read the [Pixel Perfect Rendering](../index.md) page. + +::: + +The following functions can be used to get the calculated width that the library would use for a crosshair or grid line at a specific device pixel ratio. + +```typescript +/** + * Default grid / crosshair line width in Bitmap sizing + * @param horizontalPixelRatio - horizontal pixel ratio + * @returns default grid / crosshair line width in Bitmap sizing + */ +export function gridAndCrosshairBitmapWidth( + horizontalPixelRatio: number +): number { + return Math.max(1, Math.floor(horizontalPixelRatio)); +} + +/** + * Default grid / crosshair line width in Media sizing + * @param horizontalPixelRatio - horizontal pixel ratio + * @returns default grid / crosshair line width in Media sizing + */ +export function gridAndCrosshairMediaWidth( + horizontalPixelRatio: number +): number { + return ( + gridAndCrosshairBitmapWidth(horizontalPixelRatio) / horizontalPixelRatio + ); +} + +``` diff --git a/website/docs/plugins/pixel-perfect-rendering/widths/full-bar-width.md b/website/docs/plugins/pixel-perfect-rendering/widths/full-bar-width.md new file mode 100644 index 0000000000..414f67d5f8 --- /dev/null +++ b/website/docs/plugins/pixel-perfect-rendering/widths/full-bar-width.md @@ -0,0 +1,63 @@ +--- +sidebar_position: 0 +sidebar_label: Full Bar Width +pagination_title: Full Bar Width +title: Full Bar Width Calculations +description: Describes the calculation for full bar widths +keywords: + - plugins + - extensions + - rendering + - canvas + - bitmap + - media + - pixels + - histogram + - column + - width +--- + +:::tip + +It is recommend that you first read the [Pixel Perfect Rendering](../index.md) page. + +::: + +The following functions can be used to get the calculated width that the library would use for the full width of a bar (data point) at a specific bar spacing and device pixel ratio. This can be used when you would like to use the full width available for each data point on the x axis, and don't want any gaps to be visible. + +```typescript +interface BitmapPositionLength { + /** coordinate for use with a bitmap rendering scope */ + position: number; + /** length for use with a bitmap rendering scope */ + length: number; +} + +/** + * Calculates the position and width which will completely full the space for the bar. + * Useful if you want to draw something that will not have any gaps between surrounding bars. + * @param xMedia - x coordinate of the bar defined in media sizing + * @param halfBarSpacingMedia - half the width of the current barSpacing (un-rounded) + * @param horizontalPixelRatio - horizontal pixel ratio + * @returns position and width which will completely full the space for the bar + */ +export function fullBarWidth( + xMedia: number, + halfBarSpacingMedia: number, + horizontalPixelRatio: number +): BitmapPositionLength { + const fullWidthLeftMedia = xMedia - halfBarSpacingMedia; + const fullWidthRightMedia = xMedia + halfBarSpacingMedia; + const fullWidthLeftBitmap = Math.round( + fullWidthLeftMedia * horizontalPixelRatio + ); + const fullWidthRightBitmap = Math.round( + fullWidthRightMedia * horizontalPixelRatio + ); + const fullWidthBitmap = fullWidthRightBitmap - fullWidthLeftBitmap; + return { + position: fullWidthLeftBitmap, + length: fullWidthBitmap, + }; +} +``` From 93abae8c3d0e5f6f4a9878595a8738a8dba15320 Mon Sep 17 00:00:00 2001 From: Mark Silverwood Date: Mon, 17 Jul 2023 11:42:45 +0100 Subject: [PATCH 2/3] Apply typo fixes from code review Co-authored-by: Romain Francois --- website/docs/plugins/pixel-perfect-rendering/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/plugins/pixel-perfect-rendering/index.md b/website/docs/plugins/pixel-perfect-rendering/index.md index d1017ac2de..8d6834c0ac 100644 --- a/website/docs/plugins/pixel-perfect-rendering/index.md +++ b/website/docs/plugins/pixel-perfect-rendering/index.md @@ -43,11 +43,11 @@ function centreOffset(lineBitmapWidth: number): number { /** * Calculates the bitmap position for an item with a desired length (height or width), and centred according to - * an position coordinate defined in media sizing. + * a position coordinate defined in media sizing. * @param positionMedia - position coordinate for the bar (in media coordinates) * @param pixelRatio - pixel ratio. Either horizontal for x positions, or vertical for y positions * @param desiredWidthMedia - desired width (in media coordinates) - * @returns Position of of the start point and length dimension. + * @returns Position of the start point and length dimension. */ export function positionsLine( positionMedia: number, @@ -75,7 +75,7 @@ If you need to draw a shape between two coordinates (for example, y coordinates * @param position1Media - media coordinate for the first point * @param position2Media - media coordinate for the second point * @param pixelRatio - pixel ratio for the corresponding axis (vertical or horizontal) - * @returns Position of of the start point and length dimension. + * @returns Position of the start point and length dimension. */ export function positionsBox( position1Media: number, @@ -91,7 +91,7 @@ export function positionsBox( } ``` -## Defaults Widths +## Default Widths Please refer to the following pages for functions defining the default widths of shapes drawn by the library: From 1447f646eaa1371b8d875b56775f94534dd7ede0 Mon Sep 17 00:00:00 2001 From: Mark Silverwood Date: Mon, 17 Jul 2023 11:44:41 +0100 Subject: [PATCH 3/3] applying typo fix from code review Co-authored-by: Romain Francois --- website/docs/plugins/pixel-perfect-rendering/widths/columns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/plugins/pixel-perfect-rendering/widths/columns.md b/website/docs/plugins/pixel-perfect-rendering/widths/columns.md index c2384f27de..10662adef3 100644 --- a/website/docs/plugins/pixel-perfect-rendering/widths/columns.md +++ b/website/docs/plugins/pixel-perfect-rendering/widths/columns.md @@ -216,7 +216,7 @@ export interface ColumnPositionItem { } /** - * Calculates the column positions and widths for bars using the existing the + * Calculates the column positions and widths for bars using the existing * array of items. * @param items - bar items which include an `x` property, and will be mutated to contain a column property * @param barSpacingMedia - bar spacing in media coordinates