@devexperts/dxcharts-lite 2.7.14 → 2.7.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -181,7 +181,7 @@ export default class ChartBootstrap {
181
181
  this.volumesComponent = new VolumesComponent(this.dynamicObjectsCanvasModel, chartComponent, scaleModel, canvasBoundsContainer, drawingManager, config, paneManager, this.dynamicObjects);
182
182
  this.chartComponents.push(this.volumesComponent);
183
183
  // grid component
184
- const verticalGridComponent = new GridComponent(mainCanvasModel, scaleModel, config, this.yAxisComponent.state, 'GRID', drawingManager, () => this.canvasBoundsContainer.getBounds(CanvasElement.ALL_PANES), () => this.canvasBoundsContainer.getBounds(chartPaneId), () => this.xAxisComponent.xAxisLabelsGenerator.labels, () => [], undefined, () => config.components.grid.visible);
184
+ const verticalGridComponent = new GridComponent(mainCanvasModel, scaleModel, config, this.yAxisComponent.state, 'GRID', drawingManager, () => this.canvasBoundsContainer.getBounds(CanvasElement.ALL_PANES), () => this.canvasBoundsContainer.getBounds(chartPaneId), () => this.xAxisComponent.xAxisLabelsGenerator.labels, () => [], undefined, undefined, () => config.components.grid.visible);
185
185
  this.chartComponents.push(verticalGridComponent);
186
186
  this.hoverProducer = new HoverProducerComponent(this.crossEventProducer, scaleModel, config, chartModel, canvasInputListener, this.canvasBoundsContainer, this.paneManager, timeZoneModel, chartPanComponent.mainCanvasTouchHandler, formatterFactory);
187
187
  this.chartComponents.push(this.hoverProducer);
@@ -4,7 +4,7 @@
4
4
  * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
6
  import { Subject, merge } from 'rxjs';
7
- import { distinctUntilChanged } from 'rxjs/operators';
7
+ import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
8
8
  import { CHART_UUID, CanvasElement, areBoundsChanged, } from '../../canvas/canvas-bounds-container';
9
9
  import { getDefaultConfig } from '../../chart.config';
10
10
  import { CandleSeriesModel } from '../../model/candle-series.model';
@@ -25,6 +25,7 @@ import { ChartInstrument } from './chart.component';
25
25
  import { fakeCandle } from './fake-candles';
26
26
  import { SecondaryChartColorsPool } from './secondary-chart-colors-pool';
27
27
  import { uuid } from '../../utils/uuid.utils';
28
+ import { ONE_FRAME_MS } from '../../utils/numeric-constants.utils';
28
29
  export class ChartModel extends ChartBaseElement {
29
30
  get mainCandleSeries() {
30
31
  return this.candleSeries[0];
@@ -101,6 +102,9 @@ export class ChartModel extends ChartBaseElement {
101
102
  .subscribe(bounds => {
102
103
  this.handleChartResize(bounds);
103
104
  }));
105
+ this.addRxSubscription(this.scale.changed
106
+ .pipe(debounceTime(ONE_FRAME_MS))
107
+ .subscribe(() => this.paneManager.yExtents.forEach(e => e.yAxis.updateOrderedLabels(true))));
104
108
  }
105
109
  /**
106
110
  * This method keeps the same candle width on chart canvas resize, so chart candles aren't squeezed when canvas becomes smaller
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Copyright (C) 2019 - 2025 Devexperts Solutions IE Limited
3
+ * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ export {};
@@ -0,0 +1,94 @@
1
+ /*
2
+ * Copyright (C) 2019 - 2025 Devexperts Solutions IE Limited
3
+ * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ import { treasuryPriceFormatter, parseTreasuryPrice, isTreasuryPriceFormat, TREASURY_32ND } from '../treasury-price.formatter';
7
+ describe('Treasury Price Formatter', () => {
8
+ describe('treasuryPriceFormatter', () => {
9
+ it('should format decimal prices to treasury format', () => {
10
+ expect(treasuryPriceFormatter(132.0)).toBe("132'00");
11
+ expect(treasuryPriceFormatter(132.0625)).toBe("132'02");
12
+ expect(treasuryPriceFormatter(132.3125)).toBe("132'10");
13
+ expect(treasuryPriceFormatter(132.5)).toBe("132'16");
14
+ expect(treasuryPriceFormatter(132.96875)).toBe("132'31");
15
+ });
16
+ it('should handle edge cases', () => {
17
+ expect(treasuryPriceFormatter(0)).toBe("0'00");
18
+ expect(treasuryPriceFormatter(0.03125)).toBe("0'01");
19
+ expect(treasuryPriceFormatter(1.0)).toBe("1'00");
20
+ });
21
+ });
22
+ describe('parseTreasuryPrice', () => {
23
+ it('should parse treasury format back to decimal', () => {
24
+ expect(parseTreasuryPrice("132'00")).toBe(132.0);
25
+ expect(parseTreasuryPrice("132'02")).toBe(132.0625);
26
+ expect(parseTreasuryPrice("132'10")).toBe(132.3125);
27
+ expect(parseTreasuryPrice("132'16")).toBe(132.5);
28
+ expect(parseTreasuryPrice("132'31")).toBe(132.96875);
29
+ });
30
+ it('should handle edge cases', () => {
31
+ expect(parseTreasuryPrice("0'00")).toBe(0);
32
+ expect(parseTreasuryPrice("0'01")).toBe(0.03125);
33
+ expect(parseTreasuryPrice("1'00")).toBe(1.0);
34
+ });
35
+ it('should return NaN for invalid formats', () => {
36
+ expect(parseTreasuryPrice("invalid")).toBe(NaN);
37
+ expect(parseTreasuryPrice("132'")).toBe(NaN);
38
+ expect(parseTreasuryPrice("'10")).toBe(NaN);
39
+ expect(parseTreasuryPrice("132'1")).toBe(NaN);
40
+ expect(parseTreasuryPrice("132'100")).toBe(NaN);
41
+ expect(parseTreasuryPrice("abc123")).toBe(NaN);
42
+ });
43
+ });
44
+ describe('isTreasuryPriceFormat', () => {
45
+ it('should identify valid treasury format strings', () => {
46
+ expect(isTreasuryPriceFormat("132'00")).toBe(true);
47
+ expect(isTreasuryPriceFormat("132'10")).toBe(true);
48
+ expect(isTreasuryPriceFormat("0'00")).toBe(true);
49
+ expect(isTreasuryPriceFormat("1'31")).toBe(true);
50
+ });
51
+ it('should reject invalid formats', () => {
52
+ expect(isTreasuryPriceFormat("invalid")).toBe(false);
53
+ expect(isTreasuryPriceFormat("132")).toBe(false);
54
+ expect(isTreasuryPriceFormat("132'")).toBe(false);
55
+ expect(isTreasuryPriceFormat("'10")).toBe(false);
56
+ expect(isTreasuryPriceFormat("132'1")).toBe(false);
57
+ expect(isTreasuryPriceFormat("132'100")).toBe(false);
58
+ });
59
+ });
60
+ describe('TREASURY_32ND constant', () => {
61
+ it('should equal 1/32', () => {
62
+ expect(TREASURY_32ND).toBe(1 / 32);
63
+ expect(TREASURY_32ND).toBe(0.03125);
64
+ });
65
+ });
66
+ describe('Round trip conversion', () => {
67
+ it('should maintain precision through format and parse cycle', () => {
68
+ const originalPrice = 234.3125;
69
+ const formatted = treasuryPriceFormatter(originalPrice);
70
+ const parsed = parseTreasuryPrice(formatted);
71
+ expect(parsed).toBe(originalPrice);
72
+ });
73
+ it('should work with various precision levels', () => {
74
+ const testCases = [
75
+ 132.0, // 132'00
76
+ 132.03125, // 132'01
77
+ 132.0625, // 132'02
78
+ 132.09375, // 132'03
79
+ 132.125, // 132'04
80
+ 132.15625, // 132'05
81
+ 132.1875, // 132'06
82
+ 132.21875, // 132'07
83
+ 132.25, // 132'08
84
+ 132.28125, // 132'09
85
+ 132.3125, // 132'10
86
+ ];
87
+ testCases.forEach(price => {
88
+ const formatted = treasuryPriceFormatter(price);
89
+ const parsed = parseTreasuryPrice(formatted);
90
+ expect(parsed).toBe(price);
91
+ });
92
+ });
93
+ });
94
+ });
@@ -15,3 +15,12 @@
15
15
  export declare const TREASURY_32ND: number;
16
16
  export declare const treasuryPriceFormatter: (value: number) => string;
17
17
  export declare const isTreasuryPriceFormat: (value: string) => boolean;
18
+ /**
19
+ * Parses treasury price format back to decimal number
20
+ *
21
+ * Examples:
22
+ * 132'00 => 132.0
23
+ * 132'02 => 132.0625
24
+ * 132'10 => 132.3125
25
+ */
26
+ export declare const parseTreasuryPrice: (value: string) => number;
@@ -24,4 +24,25 @@ export const treasuryPriceFormatter = (value) => {
24
24
  const thirtySecondsFormatted = thirtySeconds.toString().padStart(2, '0');
25
25
  return `${integerValue}'${thirtySecondsFormatted}`;
26
26
  };
27
- export const isTreasuryPriceFormat = (value) => Boolean(value.match(/^\-?0*(\d+)'(\d{2})$/));
27
+ const getTreasuryPriceMatch = (value) => value.match(/^\-?0*(\d+)'(\d{2})$/);
28
+ export const isTreasuryPriceFormat = (value) => Boolean(getTreasuryPriceMatch(value));
29
+ /**
30
+ * Parses treasury price format back to decimal number
31
+ *
32
+ * Examples:
33
+ * 132'00 => 132.0
34
+ * 132'02 => 132.0625
35
+ * 132'10 => 132.3125
36
+ */
37
+ export const parseTreasuryPrice = (value) => {
38
+ const match = getTreasuryPriceMatch(value);
39
+ if (match) {
40
+ const integerPart = parseInt(match[1], 10);
41
+ const thirtySeconds = parseInt(match[2], 10);
42
+ // Convert 32nds to decimal
43
+ const decimalPart = (thirtySeconds * TREASURY_32ND);
44
+ return integerPart + decimalPart;
45
+ }
46
+ // Return Number(value) for non-treasury formats (this will be NaN for invalid strings)
47
+ return Number(value);
48
+ };
@@ -15,7 +15,7 @@ export declare class GridComponent extends ChartBaseElement {
15
15
  private drawerName;
16
16
  private drawingManager;
17
17
  private readonly drawer;
18
- constructor(canvasModel: CanvasModel, viewportModel: ViewportModel, config: FullChartConfig, yAxisState: YAxisConfig, drawerName: string, drawingManager: DrawingManager | CompositeDrawer, xBoundsProvider: BoundsProvider, yBoundsProvider: BoundsProvider, xLabelsProvider: () => NumericAxisLabel[], yLabelsProvider: () => NumericAxisLabel[], getBaseline?: () => Unit, drawPredicate?: () => boolean);
18
+ constructor(canvasModel: CanvasModel, viewportModel: ViewportModel, config: FullChartConfig, yAxisState: YAxisConfig, drawerName: string, drawingManager: DrawingManager | CompositeDrawer, xBoundsProvider: BoundsProvider, yBoundsProvider: BoundsProvider, xLabelsProvider: () => NumericAxisLabel[], yLabelsProvider: () => NumericAxisLabel[], extentIdx?: number, getBaseline?: () => Unit, drawPredicate?: () => boolean);
19
19
  /**
20
20
  * This method is used to deactivate the drawer and remove it from the drawing manager.
21
21
  * @protected
@@ -6,11 +6,11 @@
6
6
  import { ChartBaseElement } from '../../model/chart-base-element';
7
7
  import { GridDrawer } from './grid.drawer';
8
8
  export class GridComponent extends ChartBaseElement {
9
- constructor(canvasModel, viewportModel, config, yAxisState, drawerName, drawingManager, xBoundsProvider, yBoundsProvider, xLabelsProvider, yLabelsProvider, getBaseline, drawPredicate) {
9
+ constructor(canvasModel, viewportModel, config, yAxisState, drawerName, drawingManager, xBoundsProvider, yBoundsProvider, xLabelsProvider, yLabelsProvider, extentIdx, getBaseline, drawPredicate) {
10
10
  super();
11
11
  this.drawerName = drawerName;
12
12
  this.drawingManager = drawingManager;
13
- this.drawer = new GridDrawer(canvasModel, viewportModel, config, yAxisState, xBoundsProvider, yBoundsProvider, xLabelsProvider, yLabelsProvider, drawPredicate, getBaseline);
13
+ this.drawer = new GridDrawer(canvasModel, viewportModel, config, yAxisState, xBoundsProvider, yBoundsProvider, xLabelsProvider, yLabelsProvider, drawPredicate, extentIdx, getBaseline);
14
14
  }
15
15
  /**
16
16
  * This method is used to deactivate the drawer and remove it from the drawing manager.
@@ -26,8 +26,9 @@ export declare class GridDrawer implements Drawer {
26
26
  private xLabelsProvider;
27
27
  private yLabelsProvider;
28
28
  private drawPredicate;
29
+ private extentIdx?;
29
30
  private getBaseline?;
30
- constructor(canvasModel: CanvasModel, viewportModel: ViewportModel, config: FullChartConfig, yAxisState: YAxisConfig, xBoundsProvider: BoundsProvider, yBoundsProvider: BoundsProvider, xLabelsProvider: () => NumericAxisLabel[], yLabelsProvider: () => NumericAxisLabel[], drawPredicate?: () => boolean, getBaseline?: (() => Unit) | undefined);
31
+ constructor(canvasModel: CanvasModel, viewportModel: ViewportModel, config: FullChartConfig, yAxisState: YAxisConfig, xBoundsProvider: BoundsProvider, yBoundsProvider: BoundsProvider, xLabelsProvider: () => NumericAxisLabel[], yLabelsProvider: () => NumericAxisLabel[], drawPredicate?: () => boolean, extentIdx?: number | undefined, getBaseline?: (() => Unit) | undefined);
31
32
  /**
32
33
  * Draws the chart on the canvas if the drawPredicate is true.
33
34
  * @returns {void}
@@ -11,7 +11,7 @@ import { floorToDPR } from '../../utils/device/device-pixel-ratio.utils';
11
11
  * Draws grid lines on chart.
12
12
  */
13
13
  export class GridDrawer {
14
- constructor(canvasModel, viewportModel, config, yAxisState, xBoundsProvider, yBoundsProvider, xLabelsProvider, yLabelsProvider, drawPredicate = () => true, getBaseline) {
14
+ constructor(canvasModel, viewportModel, config, yAxisState, xBoundsProvider, yBoundsProvider, xLabelsProvider, yLabelsProvider, drawPredicate = () => true, extentIdx, getBaseline) {
15
15
  this.canvasModel = canvasModel;
16
16
  this.viewportModel = viewportModel;
17
17
  this.config = config;
@@ -21,6 +21,7 @@ export class GridDrawer {
21
21
  this.xLabelsProvider = xLabelsProvider;
22
22
  this.yLabelsProvider = yLabelsProvider;
23
23
  this.drawPredicate = drawPredicate;
24
+ this.extentIdx = extentIdx;
24
25
  this.getBaseline = getBaseline;
25
26
  }
26
27
  /**
@@ -41,7 +42,8 @@ export class GridDrawer {
41
42
  */
42
43
  drawZeroLine(ctx) {
43
44
  const yAxisLabels = this.yLabelsProvider();
44
- if (this.getBaseline &&
45
+ if (this.extentIdx === 0 &&
46
+ this.getBaseline &&
45
47
  this.yAxisState.type === 'percent' &&
46
48
  this.yAxisState.zeroPercentLine &&
47
49
  yAxisLabels.length) {
@@ -94,9 +94,9 @@ export class PaneComponent extends ChartBaseElement {
94
94
  * @param {() => Unit} yAxisBaselineGetter
95
95
  * @returns {GridComponent} - The newly created GridComponent instance.
96
96
  */
97
- createGridComponent(uuid, scale, yAxisState, yAxisLabelsGetter, yAxisBaselineGetter) {
97
+ createGridComponent(uuid, extentIdx, scale, yAxisState, yAxisLabelsGetter, yAxisBaselineGetter) {
98
98
  const chartPaneId = CanvasElement.PANE_UUID(uuid);
99
- const gridComponent = new GridComponent(this.mainCanvasModel, scale, this.config, yAxisState, `PANE_${uuid}_grid_drawer`, this.drawingManager, () => this.canvasBoundsContainer.getBounds(chartPaneId), () => this.canvasBoundsContainer.getBounds(chartPaneId), () => [], yAxisLabelsGetter, yAxisBaselineGetter, () => this.config.components.grid.visible);
99
+ const gridComponent = new GridComponent(this.mainCanvasModel, scale, this.config, yAxisState, `PANE_${uuid}_${extentIdx}_grid_drawer`, this.drawingManager, () => this.canvasBoundsContainer.getBounds(chartPaneId), () => this.canvasBoundsContainer.getBounds(chartPaneId), () => [], yAxisLabelsGetter, extentIdx, yAxisBaselineGetter, () => this.config.components.grid.visible);
100
100
  return gridComponent;
101
101
  }
102
102
  /**
@@ -132,7 +132,7 @@ export class PaneComponent extends ChartBaseElement {
132
132
  const [unsub, dragNDrop] = this.createYPanHandler(this.uuid, scaleModel);
133
133
  // creating partially resolved constructor except formatter & dataSeriesProvider - bcs it's not possible to provide formatter
134
134
  // before y-extent is created
135
- const createYAxisComponent = (formatter, dataSeriesProvider) => new YAxisComponent(this.eventBus, this.config, this.yAxisLabelsCanvasModel, scaleModel, this.canvasInputListener, this.canvasBoundsContainer, this.chartPanComponent, this.cursorHandler, formatter, dataSeriesProvider, this.uuid, extentIdx, this.hitTestCanvasModel, this.chartResizeHandler, initialYAxisState);
135
+ const createYAxisComponent = (formatter, dataSeriesProvider) => new YAxisComponent(this.eventBus, this.config, this.mainCanvasModel, this.yAxisLabelsCanvasModel, scaleModel, this.canvasInputListener, this.canvasBoundsContainer, this.chartPanComponent, this.cursorHandler, formatter, dataSeriesProvider, this.uuid, extentIdx, this.hitTestCanvasModel, this.chartResizeHandler, initialYAxisState);
136
136
  const yExtentComponent = new YExtentComponent(this.config.components.yAxis, this.uuid, extentIdx, this, this.chartBaseModel, this.canvasBoundsContainer, this.hitTestController, this.dynamicObjectsCanvasModel, scaleModel, createYAxisComponent, dragNDrop);
137
137
  yExtentComponent.addSubscription(unsub);
138
138
  yExtentComponent.addSubscription(this.addCursors(extentIdx, yExtentComponent.yAxis));
@@ -143,7 +143,7 @@ export class PaneComponent extends ChartBaseElement {
143
143
  if (useDefaultHighLow) {
144
144
  scaleModel.autoScaleModel.setHighLowProvider('default', createCandlesOffsetProvider(() => ({ top: 10, bottom: 10, left: 0, right: 0, visible: true }), createDefaultYExtentHighLowProvider(yExtentComponent)));
145
145
  }
146
- const gridComponent = this.createGridComponent(this.uuid, scaleModel, yExtentComponent.yAxis.state, () => yExtentComponent.yAxis.model.baseLabelsModel.labels, () => yExtentComponent.toY(yExtentComponent.getBaseline()));
146
+ const gridComponent = this.createGridComponent(this.uuid, yExtentComponent.idx, scaleModel, yExtentComponent.yAxis.state, () => yExtentComponent.yAxis.model.baseLabelsModel.labels, () => yExtentComponent.toY(yExtentComponent.getBaseline()));
147
147
  yExtentComponent.addChildEntity(gridComponent);
148
148
  yExtentComponent.activate();
149
149
  this.yExtentComponents.push(yExtentComponent);
@@ -55,7 +55,9 @@ export class YAxisBaseLabelsModel extends ChartBaseElement {
55
55
  * Then, it calls the updateYAxisWidth method to update the width of the y-axis.
56
56
  */
57
57
  updateLabels() {
58
- this.labels = this.labelsGenerator.generateNumericLabels();
59
- animationFrameThrottledPrior(this.animFrameId, () => this.canvasBoundsContainer.updateYAxisWidths());
58
+ animationFrameThrottledPrior(this.animFrameId, () => {
59
+ this.labels = this.labelsGenerator.generateNumericLabels();
60
+ this.canvasBoundsContainer.updateYAxisWidths();
61
+ });
60
62
  }
61
63
  }
@@ -30,6 +30,7 @@ export type LabelColorResolver = (priceMovement: PriceMovement, colors: FullChar
30
30
  export declare class YAxisComponent extends ChartBaseElement {
31
31
  private eventBus;
32
32
  private config;
33
+ private mainCanvasModel;
33
34
  private canvasModel;
34
35
  scale: ScaleModel;
35
36
  private canvasInputListeners;
@@ -47,7 +48,7 @@ export declare class YAxisComponent extends ChartBaseElement {
47
48
  axisAlignSetSubject: Subject<YAxisAlign>;
48
49
  axisAlignMovedSubject: Subject<YAxisAlign>;
49
50
  readonly state: YAxisConfig;
50
- constructor(eventBus: EventBus, config: FullChartConfig, canvasModel: CanvasModel, scale: ScaleModel, canvasInputListeners: CanvasInputListenerComponent, canvasBoundsContainer: CanvasBoundsContainer, chartPanComponent: ChartPanComponent, cursors: CursorHandler, valueFormatter: (value: number) => string, dataSeriesProvider: () => DataSeriesModel | undefined, paneUUID: string, extentIdx: number, hitTestCanvasModel: HitTestCanvasModel, chartResizeHandler: ChartResizeHandler, initialState?: YAxisConfig);
51
+ constructor(eventBus: EventBus, config: FullChartConfig, mainCanvasModel: CanvasModel, canvasModel: CanvasModel, scale: ScaleModel, canvasInputListeners: CanvasInputListenerComponent, canvasBoundsContainer: CanvasBoundsContainer, chartPanComponent: ChartPanComponent, cursors: CursorHandler, valueFormatter: (value: number) => string, dataSeriesProvider: () => DataSeriesModel | undefined, paneUUID: string, extentIdx: number, hitTestCanvasModel: HitTestCanvasModel, chartResizeHandler: ChartResizeHandler, initialState?: YAxisConfig);
51
52
  setExtentIdx(extentIdx: number): void;
52
53
  /**
53
54
  * Registers default label color resolvers for different chart types.
@@ -17,10 +17,11 @@ import { cloneUnsafe } from '../../utils/object.utils';
17
17
  * Y axis component. Contains all Y axis related logic.
18
18
  */
19
19
  export class YAxisComponent extends ChartBaseElement {
20
- constructor(eventBus, config, canvasModel, scale, canvasInputListeners, canvasBoundsContainer, chartPanComponent, cursors, valueFormatter, dataSeriesProvider, paneUUID, extentIdx, hitTestCanvasModel, chartResizeHandler, initialState) {
20
+ constructor(eventBus, config, mainCanvasModel, canvasModel, scale, canvasInputListeners, canvasBoundsContainer, chartPanComponent, cursors, valueFormatter, dataSeriesProvider, paneUUID, extentIdx, hitTestCanvasModel, chartResizeHandler, initialState) {
21
21
  super();
22
22
  this.eventBus = eventBus;
23
23
  this.config = config;
24
+ this.mainCanvasModel = mainCanvasModel;
24
25
  this.canvasModel = canvasModel;
25
26
  this.scale = scale;
26
27
  this.canvasInputListeners = canvasInputListeners;
@@ -197,6 +198,7 @@ export class YAxisComponent extends ChartBaseElement {
197
198
  this.scale.autoScale(true);
198
199
  this.model.fancyLabelsModel.updateLabels(true);
199
200
  this.updateCursor();
201
+ this.mainCanvasModel.fireDraw();
200
202
  }
201
203
  }
202
204
  /**