1+ import { ensure } from '../helpers/assertions' ;
12import { min } from '../helpers/mathex' ;
23
34import { Coordinate } from './coordinate' ;
45import { PriceMark , PriceScale } from './price-scale' ;
6+ import { convertPriceRangeFromLog } from './price-scale-conversions' ;
57import { PriceTickSpanCalculator } from './price-tick-span-calculator' ;
68
79export type CoordinateToLogicalConverter = ( x : number , firstValue : number ) => number ;
@@ -74,12 +76,51 @@ export class PriceTickMarkBuilder {
7476
7577 const high = Math . max ( bottom , top ) ;
7678 const low = Math . min ( bottom , top ) ;
79+
7780 if ( high === low ) {
7881 this . _marks = [ ] ;
7982 return ;
8083 }
8184
82- let span = this . tickSpan ( high , low ) ;
85+ const span = this . tickSpan ( high , low ) ;
86+ this . _updateMarks (
87+ firstValue ,
88+ span ,
89+ high ,
90+ low ,
91+ minCoord ,
92+ maxCoord
93+ ) ;
94+
95+ if ( priceScale . hasVisibleEdgeMarks ( ) && this . _shouldApplyEdgeMarks ( span , low , high ) ) {
96+ const padding = this . _priceScale . getEdgeMarksPadding ( ) ;
97+ this . _applyEdgeMarks (
98+ firstValue ,
99+ span ,
100+ minCoord ,
101+ maxCoord ,
102+ padding ,
103+ padding * 2
104+ ) ;
105+ }
106+ }
107+
108+ public marks ( ) : PriceMark [ ] {
109+ return this . _marks ;
110+ }
111+
112+ private _fontHeight ( ) : number {
113+ return this . _priceScale . fontSize ( ) ;
114+ }
115+
116+ private _tickMarkHeight ( ) : number {
117+ return Math . ceil ( this . _fontHeight ( ) * TICK_DENSITY ) ;
118+ }
119+
120+ private _updateMarks ( firstValue : number , span : number , high : number , low : number , minCoord : number , maxCoord : number ) : void {
121+ const marks = this . _marks ;
122+ const priceScale = this . _priceScale ;
123+
83124 let mod = high % span ;
84125 mod += mod < 0 ? span : 0 ;
85126
@@ -102,11 +143,11 @@ export class PriceTickMarkBuilder {
102143 continue ;
103144 }
104145
105- if ( targetIndex < this . _marks . length ) {
106- this . _marks [ targetIndex ] . coord = coord as Coordinate ;
107- this . _marks [ targetIndex ] . label = priceScale . formatLogical ( logical ) ;
146+ if ( targetIndex < marks . length ) {
147+ marks [ targetIndex ] . coord = coord as Coordinate ;
148+ marks [ targetIndex ] . label = priceScale . formatLogical ( logical ) ;
108149 } else {
109- this . _marks . push ( {
150+ marks . push ( {
110151 coord : coord as Coordinate ,
111152 label : priceScale . formatLogical ( logical ) ,
112153 } ) ;
@@ -120,18 +161,77 @@ export class PriceTickMarkBuilder {
120161 span = this . tickSpan ( logical * sign , low ) ;
121162 }
122163 }
123- this . _marks . length = targetIndex ;
164+
165+ marks . length = targetIndex ;
124166 }
125167
126- public marks ( ) : PriceMark [ ] {
127- return this . _marks ;
168+ private _applyEdgeMarks (
169+ firstValue : number ,
170+ span : number ,
171+ minCoord : number ,
172+ maxCoord : number ,
173+ minPadding : number ,
174+ maxPadding : number
175+ ) : void {
176+ const marks = this . _marks ;
177+ // top boundary
178+ const topMark = this . _computeBoundaryPriceMark (
179+ firstValue ,
180+ minCoord ,
181+ minPadding ,
182+ maxPadding
183+ ) ;
184+
185+ // bottom boundary
186+ const bottomMark = this . _computeBoundaryPriceMark (
187+ firstValue ,
188+ maxCoord ,
189+ - maxPadding ,
190+ - minPadding
191+ ) ;
192+
193+ const spanPx = this . _logicalToCoordinateFunc ( 0 , firstValue , true )
194+ - this . _logicalToCoordinateFunc ( span , firstValue , true ) ;
195+
196+ if ( marks . length > 0 && marks [ 0 ] . coord - topMark . coord < spanPx / 2 ) {
197+ marks . shift ( ) ;
198+ }
199+
200+ if ( marks . length > 0 && bottomMark . coord - marks [ marks . length - 1 ] . coord < spanPx / 2 ) {
201+ marks . pop ( ) ;
202+ }
203+
204+ marks . unshift ( topMark ) ;
205+ marks . push ( bottomMark ) ;
128206 }
129207
130- private _fontHeight ( ) : number {
131- return this . _priceScale . fontSize ( ) ;
208+ private _computeBoundaryPriceMark (
209+ firstValue : number ,
210+ coord : number ,
211+ minPadding : number ,
212+ maxPadding : number
213+ ) : PriceMark {
214+ const avgPadding = ( minPadding + maxPadding ) / 2 ;
215+ const value1 = this . _coordinateToLogicalFunc ( coord + minPadding , firstValue ) ;
216+ const value2 = this . _coordinateToLogicalFunc ( coord + maxPadding , firstValue ) ;
217+ const minValue = Math . min ( value1 , value2 ) ;
218+ const maxValue = Math . max ( value1 , value2 ) ;
219+ const valueSpan = Math . max ( 0.1 , this . tickSpan ( maxValue , minValue ) ) ;
220+
221+ const value = this . _coordinateToLogicalFunc ( coord + avgPadding , firstValue ) ;
222+ const roundedValue = value - ( value % valueSpan ) ;
223+ const roundedCoord = this . _logicalToCoordinateFunc ( roundedValue , firstValue , true ) ;
224+
225+ return { label : this . _priceScale . formatLogical ( roundedValue ) , coord : roundedCoord as Coordinate } ;
132226 }
133227
134- private _tickMarkHeight ( ) : number {
135- return Math . ceil ( this . _fontHeight ( ) * TICK_DENSITY ) ;
228+ private _shouldApplyEdgeMarks ( span : number , low : number , high : number ) : boolean {
229+ let range = ensure ( this . _priceScale . priceRange ( ) ) ;
230+
231+ if ( this . _priceScale . isLog ( ) ) {
232+ range = convertPriceRangeFromLog ( range , this . _priceScale . getLogFormula ( ) ) ;
233+ }
234+
235+ return ( range . minValue ( ) - low < span ) && ( high - range . maxValue ( ) < span ) ;
136236 }
137237}
0 commit comments