@stellar-expert/ui-framework 1.21.2 → 1.21.3

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.
@@ -131,6 +131,21 @@ export class Axis {
131
131
  this.dataMax = dataMax
132
132
  }
133
133
 
134
+ //pixels at the top of the pane that must stay clear of drawn data — the floating range-selector
135
+ //buttons overlay the top ~26px of the plot, so the pane that starts at the plot top scales its
136
+ //max up until the tallest point (incl. candle wicks) renders below the controls
137
+ reservedTopPixels() {
138
+ const c = this.chart
139
+ if (this.isXAxis || c.chartType !== 'StockChart' || !c.options.rangeSelector)
140
+ return 0
141
+ if (!isNumber(this.len) || !isNumber(this.top) || !isNumber(c.plotTop))
142
+ return 0
143
+ //only the top pane sits under the controls; lower panes (e.g. a volume strip) are unaffected
144
+ if (this.top > c.plotTop + 1)
145
+ return 0
146
+ return 26
147
+ }
148
+
134
149
  setScale() {
135
150
  this.getSeriesExtremes()
136
151
  let min = pick(this.userMin, this.dataMin)
@@ -226,7 +241,12 @@ export class Axis {
226
241
  drawn = drawn.slice(0, -1)
227
242
  const nIntervals = drawn.length - 1
228
243
  const dataSteps = r.interval > 0 ? (max - r.min) / r.interval : nIntervals
229
- const M = Math.max(nIntervals + 0.7, dataSteps + 0.15)
244
+ let M = Math.max(nIntervals + 0.7, dataSteps + 0.15)
245
+ //widen the headroom so the data top maps below the reserved zoom-controls band:
246
+ //(M - dataSteps)/M of the pane height must be at least reserve/len
247
+ const reserve = this.reservedTopPixels()
248
+ if (reserve && this.len > reserve * 2)
249
+ M = Math.max(M, dataSteps / (1 - reserve / this.len))
230
250
  this.tickInterval = r.interval
231
251
  this.tickPositions = drawn
232
252
  this.labelledPositions = null
@@ -62,9 +62,15 @@ export class Chart {
62
62
  this.hideLoading()
63
63
  const el = document.createElement('div')
64
64
  el.className = 'ix-chart-loading'
65
- el.textContent = text || 'Loading…'
65
+ if (text)
66
+ el.textContent = text
66
67
  Object.assign(el.style, {
67
- position: 'absolute', inset: '0', display: 'flex', alignItems: 'center',
68
+ position: 'absolute',
69
+ left: (this.plotLeft || 0) + 'px',
70
+ top: (this.plotTop || 0) + 'px',
71
+ width: (this.plotWidth || 0) + 'px',
72
+ height: (this.plotHeight || 0) + 'px',
73
+ display: 'flex', alignItems: 'center',
68
74
  justifyContent: 'center', background: 'rgba(0,0,0,0.25)', color: 'var(--color-text)', zIndex: 15
69
75
  })
70
76
  this.container.appendChild(el)
@@ -266,6 +272,10 @@ export class Chart {
266
272
  const nIntervals = s.drawn.length - 1
267
273
  const dataSteps = s.interval > 0 ? (s.dataMax - s.base) / s.interval : nIntervals
268
274
  M = Math.max(M, nIntervals + STEP_HEAD, dataSteps + 0.15)
275
+ //keep the data top below the reserved zoom-controls band (see Axis.reservedTopPixels)
276
+ const reserve = s.a.reservedTopPixels()
277
+ if (reserve && s.a.len > reserve * 2)
278
+ M = Math.max(M, dataSteps / (1 - reserve / s.a.len))
269
279
  }
270
280
  for (const s of specs) {
271
281
  s.a.tickInterval = s.interval
@@ -40,7 +40,7 @@ export class CandlestickSeries extends Series {
40
40
  continue
41
41
  const cx = xAxis.toPixels(p.x)
42
42
  const catW = Math.abs(xAxis.toPixels(p.x + pr) - xAxis.toPixels(p.x))
43
- const w = Math.max(1, catW * 0.6)
43
+ const w = Math.max(1.5, catW * 0.8 - 0.5)
44
44
  const up = p.close >= p.open
45
45
  const color = up ? upColor : downColor
46
46
  const yHigh = yAxis.toPixels(p.high)
@@ -57,7 +57,8 @@ export class Navigator {
57
57
  const yToPx = v => top + height - ((v - yMin) / ((yMax - yMin) || 1)) * (height - 4) - 2
58
58
 
59
59
  //faint background
60
- renderer.rect(left, top, width, height, {}).css({fill: 'var(--color-border-shadow)', 'fill-opacity': 0.15}).add(group)
60
+ const bg = renderer.rect(left, top, width, height, {}).css({fill: 'var(--color-border-shadow)', 'fill-opacity': 0.15, cursor: 'pointer'}).add(group)
61
+ this.bindBackground(bg)
61
62
  //time breakpoints across the full overview range, so it's clear where the selected window sits
62
63
  this.renderTimeAxis(group, top, height, dMin, dMax, xToPx)
63
64
  //overview as a thin line (no fill)
@@ -70,7 +71,7 @@ export class Navigator {
70
71
  started = true
71
72
  }
72
73
  }
73
- const lineEl = renderer.path(d.trim(), {'stroke-width': 1, fill: 'none'}).css({stroke: NAV_LINE}).add(group)
74
+ const lineEl = renderer.path(d.trim(), {'stroke-width': 1, fill: 'none'}).css({stroke: NAV_LINE, 'pointer-events': 'none'}).add(group)
74
75
 
75
76
  //selection window edges (clamped into the strip) — use the LOGICAL extremes (pre display-padding)
76
77
  const x0 = clamp(xToPx(viewMin(xAxis)), left, left + width)
@@ -131,6 +132,33 @@ export class Navigator {
131
132
  return rect
132
133
  }
133
134
 
135
+ //click on the unselected part of the strip: move the selection window (same span) so it centers
136
+ //on the clicked date, clamped to the data edges
137
+ bindBackground(bg) {
138
+ const chart = this.chart
139
+ const xAxis = chart.xAxis[0]
140
+ bg.on('mousedown', e => {
141
+ e.preventDefault()
142
+ const svg = chart.renderer.root.element
143
+ const rect = svg.getBoundingClientRect()
144
+ const scaleX = chart.chartWidth / rect.width
145
+ const px = (e.clientX - rect.left) * scaleX
146
+ const v = this.dataMin + ((px - this.left) / this.width) * (this.dataMax - this.dataMin)
147
+ const span = viewMax(xAxis) - viewMin(xAxis)
148
+ let min = v - span / 2
149
+ let max = v + span / 2
150
+ if (min < this.dataMin) {
151
+ min = this.dataMin
152
+ max = min + span
153
+ }
154
+ if (max > this.dataMax) {
155
+ max = this.dataMax
156
+ min = max - span
157
+ }
158
+ xAxis.setExtremes(min, max)
159
+ })
160
+ }
161
+
134
162
  bindBand(band) {
135
163
  const chart = this.chart
136
164
  const xAxis = chart.xAxis[0]
@@ -88,18 +88,40 @@ export class RangeSelector {
88
88
  this.zoomLabel.style.display = compact ? 'none' : ''
89
89
  }
90
90
 
91
+ //full data range, unioned with the navigator's own series. Views that lazy-load data on zoom replace
92
+ //the main series with just the visible sub-window (keeping the full lifespan only in navigator.series),
93
+ //so the axis' dataMin/dataMax shrink to the loaded slice — anchoring the buttons on those would make
94
+ //'All' re-select the current window instead of the whole history
95
+ getUnionExtremes() {
96
+ const xAxis = this.chart.xAxis[0]
97
+ let {dataMin, dataMax} = xAxis
98
+ const navSeries = this.chart.options.navigator && this.chart.options.navigator.series
99
+ const navData = navSeries && navSeries.data
100
+ if (navData && navData.length) {
101
+ for (const p of navData) {
102
+ const x = Array.isArray(p) ? p[0] : p && p.x
103
+ if (!isNumber(x))
104
+ continue
105
+ if (!isNumber(dataMin) || x < dataMin) dataMin = x
106
+ if (!isNumber(dataMax) || x > dataMax) dataMax = x
107
+ }
108
+ }
109
+ return {dataMin, dataMax}
110
+ }
111
+
91
112
  apply(b, i) {
92
113
  const xAxis = this.chart.xAxis[0]
114
+ const {dataMin, dataMax} = this.getUnionExtremes()
93
115
  if (b.type === 'all') {
94
116
  this.selected = 'all'
95
117
  //explicit full extremes so the initial xAxis.range window doesn't re-apply
96
- xAxis.setExtremes(xAxis.dataMin, xAxis.dataMax)
118
+ xAxis.setExtremes(dataMin, dataMax)
97
119
  } else {
98
120
  const count = b.count || 1
99
121
  //the window always ENDS at the last data point; going back a calendar
100
122
  //month/year/day for the start. Anchoring the end on dataMax keeps the right edge flush with the
101
123
  //data instead of snapping forward to the next month boundary (which left a gap on the right).
102
- const max = xAxis.dataMax
124
+ const max = dataMax
103
125
  const d = new Date(max)
104
126
  if (b.type === 'year')
105
127
  d.setUTCFullYear(d.getUTCFullYear() - count)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stellar-expert/ui-framework",
3
- "version": "1.21.2",
3
+ "version": "1.21.3",
4
4
  "description": "StellarExpert shared UI components library",
5
5
  "author": "orbitlens <orbit@stellar.expert>",
6
6
  "license": "MIT",