@coinbase/cds-mobile-visualization 3.4.0-beta.10 → 3.4.0-beta.12

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.
@@ -3,10 +3,41 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
3
3
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
4
4
  import { useCallback, useMemo, useState } from 'react';
5
5
  import { getChartDomain, getChartRange, isValidBounds } from './chart';
6
+ import { getPointOnScale } from './point';
6
7
  import { getCategoricalScale, getNumericScale, isCategoricalScale, isNumericScale } from './scale';
7
8
  export const defaultAxisId = 'DEFAULT_AXIS_ID';
8
9
  export const defaultAxisScaleType = 'linear';
9
10
 
11
+ /**
12
+ * Position options for band scale axis elements (grid lines, tick marks, labels).
13
+ *
14
+ * - `'start'` - At the start of each step (before bar padding)
15
+ * - `'middle'` - At the center of each band
16
+ * - `'end'` - At the end of each step (after bar padding)
17
+ * - `'edges'` - At start of each tick, plus end for the last tick (encloses the chart)
18
+ *
19
+ * @note These properties only apply when using a band scale (`scaleType: 'band'`).
20
+ */
21
+
22
+ /**
23
+ * Converts an AxisBandPlacement to the corresponding PointAnchor for use with getPointOnScale.
24
+ *
25
+ * @param placement - The axis placement value
26
+ * @returns The corresponding PointAnchor for scale calculations
27
+ */
28
+ export const toPointAnchor = placement => {
29
+ switch (placement) {
30
+ case 'edges': // edges uses stepStart for each tick, stepEnd handled separately
31
+ case 'start':
32
+ return 'stepStart';
33
+ case 'end':
34
+ return 'stepEnd';
35
+ case 'middle':
36
+ default:
37
+ return 'middle';
38
+ }
39
+ };
40
+
10
41
  /**
11
42
  * Axis configuration with computed bounds
12
43
  */
@@ -457,6 +488,7 @@ const generateEvenlyDistributedTicks = (scale, tickInterval, possibleTickValues,
457
488
  * // Returns tick positions centered in each selected band
458
489
  */
459
490
  export const getAxisTicksData = _ref4 => {
491
+ var _options$anchor;
460
492
  let {
461
493
  ticks,
462
494
  scaleFunction,
@@ -466,53 +498,37 @@ export const getAxisTicksData = _ref4 => {
466
498
  tickInterval,
467
499
  options
468
500
  } = _ref4;
501
+ const anchor = (_options$anchor = options == null ? void 0 : options.anchor) != null ? _options$anchor : 'middle';
502
+
469
503
  // Handle band scales
470
504
  if (isCategoricalScale(scaleFunction)) {
505
+ const bandScale = scaleFunction;
506
+
471
507
  // If explicit ticks are provided as array, use them
472
508
  if (Array.isArray(ticks)) {
473
- return ticks.filter(index => index >= 0 && index < categories.length).map(index => {
474
- var _bandwidth;
475
- // Band scales expect numeric indices, not category strings
476
- const position = scaleFunction(index);
477
- if (position === undefined) return null;
478
- return {
479
- tick: index,
480
- position: position + ((_bandwidth = scaleFunction.bandwidth == null ? void 0 : scaleFunction.bandwidth()) != null ? _bandwidth : 0) / 2
481
- };
482
- }).filter(Boolean);
509
+ return ticks.filter(index => index >= 0 && index < categories.length).map(index => ({
510
+ tick: index,
511
+ position: getPointOnScale(index, bandScale, anchor)
512
+ }));
483
513
  }
484
514
 
485
515
  // If a tick function is provided, use it to filter
486
516
  if (typeof ticks === 'function') {
487
517
  return categories.map((category, index) => {
488
- var _bandwidth2;
489
518
  if (!ticks(index)) return null;
490
-
491
- // Band scales expect numeric indices, not category strings
492
- const position = scaleFunction(index);
493
- if (position === undefined) return null;
494
519
  return {
495
520
  tick: index,
496
- position: position + ((_bandwidth2 = scaleFunction.bandwidth == null ? void 0 : scaleFunction.bandwidth()) != null ? _bandwidth2 : 0) / 2
521
+ position: getPointOnScale(index, bandScale, anchor)
497
522
  };
498
523
  }).filter(Boolean);
499
524
  }
500
- if (typeof ticks === 'boolean' && !ticks) {
501
- return [];
502
- }
503
525
 
504
526
  // For band scales without explicit ticks, show all categories
505
527
  // requestedTickCount is ignored for categorical scales - use ticks parameter to control visibility
506
- return categories.map((category, index) => {
507
- var _bandwidth3;
508
- // Band scales expect numeric indices, not category strings
509
- const position = scaleFunction(index);
510
- if (position === undefined) return null;
511
- return {
512
- tick: index,
513
- position: position + ((_bandwidth3 = scaleFunction.bandwidth == null ? void 0 : scaleFunction.bandwidth()) != null ? _bandwidth3 : 0) / 2
514
- };
515
- }).filter(Boolean);
528
+ return categories.map((_, index) => ({
529
+ tick: index,
530
+ position: getPointOnScale(index, bandScale, anchor)
531
+ }));
516
532
  }
517
533
 
518
534
  // Handle numeric scales
@@ -1,4 +1,4 @@
1
- import { applySerializableScale, isCategoricalScale, isLogScale, isNumericScale } from './scale';
1
+ import { applyBandScale, applySerializableScale, isCategoricalScale, isLogScale, isNumericScale } from './scale';
2
2
 
3
3
  /**
4
4
  * Position a label should be placed relative to the point
@@ -10,19 +10,38 @@ import { applySerializableScale, isCategoricalScale, isLogScale, isNumericScale
10
10
 
11
11
  /**
12
12
  * Get a point from a data value and a scale.
13
- * @note for categorical scales, the point will be centered within the band.
14
- * @note for log scales, zero and negative values are clamped to a small positive value.
15
- * @param data - the data value.
16
- * @param scale - the scale function.
17
- * @returns the pixel value (defaulting to 0 if data value is not defined in scale).
13
+ *
14
+ * @param dataValue - The data value to convert to a pixel position.
15
+ * @param scale - The scale function.
16
+ * @param anchor (@default 'middle') - For band scales, where to anchor the point within the band.
17
+ * @returns The pixel value (@default 0 if data value is not defined in scale).
18
18
  */
19
- export const getPointOnScale = (dataValue, scale) => {
20
- var _scale2;
19
+ export const getPointOnScale = function (dataValue, scale, anchor) {
20
+ var _scale;
21
+ if (anchor === void 0) {
22
+ anchor = 'middle';
23
+ }
21
24
  if (isCategoricalScale(scale)) {
22
- var _scale, _scale$bandwidth;
23
- const bandStart = (_scale = scale(dataValue)) != null ? _scale : 0;
24
- const bandwidth = (_scale$bandwidth = scale.bandwidth()) != null ? _scale$bandwidth : 0;
25
- return bandStart + bandwidth / 2;
25
+ var _bandScale$bandwidth, _bandScale$step;
26
+ const bandScale = scale;
27
+ const bandStart = bandScale(dataValue);
28
+ if (bandStart === undefined) return 0;
29
+ const bandwidth = (_bandScale$bandwidth = bandScale.bandwidth == null ? void 0 : bandScale.bandwidth()) != null ? _bandScale$bandwidth : 0;
30
+ const step = (_bandScale$step = bandScale.step == null ? void 0 : bandScale.step()) != null ? _bandScale$step : bandwidth;
31
+ const paddingOffset = (step - bandwidth) / 2;
32
+ const stepStart = bandStart - paddingOffset;
33
+ switch (anchor) {
34
+ case 'stepStart':
35
+ return stepStart;
36
+ case 'bandStart':
37
+ return bandStart;
38
+ case 'middle':
39
+ return bandStart + bandwidth / 2;
40
+ case 'bandEnd':
41
+ return bandStart + bandwidth;
42
+ case 'stepEnd':
43
+ return stepStart + step;
44
+ }
26
45
  }
27
46
 
28
47
  // For log scales, ensure the value is positive
@@ -30,23 +49,47 @@ export const getPointOnScale = (dataValue, scale) => {
30
49
  if (isLogScale(scale) && dataValue <= 0) {
31
50
  adjustedValue = 0.001; // Use a small positive value for log scales
32
51
  }
33
- return (_scale2 = scale(adjustedValue)) != null ? _scale2 : 0;
52
+ return (_scale = scale(adjustedValue)) != null ? _scale : 0;
34
53
  };
35
54
 
36
55
  /**
37
56
  * Get a point from a data value and a serializable scale (worklet-compatible).
38
- * @note for categorical scales, the point will be centered within the band.
39
- * @note for log scales, zero and negative values are clamped to a small positive value.
40
- * @param dataValue - the data value.
41
- * @param scale - the serializable scale object.
42
- * @returns the pixel value (defaulting to 0 if data value is not defined in scale).
57
+ *
58
+ * @param dataValue - The data value to convert to a pixel position.
59
+ * @param scale - The serializable scale function.
60
+ * @param anchor (@default 'middle') - For band scales, where to anchor the point within the band.
61
+ * @returns The pixel value (@default 0 if data value is not defined in scale).
43
62
  */
44
- export function getPointOnSerializableScale(dataValue, scale) {
63
+ export function getPointOnSerializableScale(dataValue, scale, anchor) {
45
64
  'worklet';
46
65
 
66
+ // Handle band scales with the specified position
67
+ if (anchor === void 0) {
68
+ anchor = 'middle';
69
+ }
47
70
  if (scale.type === 'band') {
48
- const bandStart = applySerializableScale(dataValue, scale);
49
- return bandStart + scale.bandwidth / 2;
71
+ const bandScale = scale;
72
+ const [domainMin, domainMax] = bandScale.domain;
73
+ const index = dataValue - domainMin;
74
+ const n = domainMax - domainMin + 1;
75
+ if (index < 0 || index >= n) {
76
+ return 0;
77
+ }
78
+ const bandStart = applyBandScale(dataValue, bandScale);
79
+ const paddingOffset = (bandScale.step - bandScale.bandwidth) / 2;
80
+ const stepStart = bandStart - paddingOffset;
81
+ switch (anchor) {
82
+ case 'stepStart':
83
+ return stepStart;
84
+ case 'bandStart':
85
+ return bandStart;
86
+ case 'middle':
87
+ return bandStart + bandScale.bandwidth / 2;
88
+ case 'bandEnd':
89
+ return bandStart + bandScale.bandwidth;
90
+ case 'stepEnd':
91
+ return stepStart + bandScale.step;
92
+ }
50
93
  }
51
94
 
52
95
  // For log scales, ensure the value is positive
@@ -53,10 +53,21 @@ export const getCategoricalScale = _ref2 => {
53
53
  const domainArray = Array.from({
54
54
  length: domain.max - domain.min + 1
55
55
  }, (_, i) => i);
56
- const scale = scaleBand().domain(domainArray).range([range.min, range.max]).padding(padding);
56
+ const scale = scaleBand().domain(domainArray).range([range.min, range.max]).paddingInner(padding).paddingOuter(padding / 2);
57
57
  return scale;
58
58
  };
59
59
 
60
+ /**
61
+ * Anchor position for points on a scale. Currently used only for band scales.
62
+ *
63
+ * For band scales, this determines where within the band to position a point:
64
+ * - `'stepStart'` - At the start of the step
65
+ * - `'bandStart'` - At the start of the band
66
+ * - `'middle'` - At the center of the band
67
+ * - `'bandEnd'` - At the end of the band
68
+ * - `'stepEnd'` - At the end of the step
69
+ */
70
+
60
71
  /**
61
72
  * Convert a D3 scale to a serializable scale configuration that can be used in worklets
62
73
  */
@@ -176,7 +187,7 @@ export function applyBandScale(value, scale) {
176
187
  if (index < 0 || index >= n) {
177
188
  return r0;
178
189
  }
179
- const paddingOffset = step - scale.bandwidth;
190
+ const paddingOffset = (step - scale.bandwidth) / 2;
180
191
  const bandStart = r0 + step * index + paddingOffset;
181
192
  return bandStart;
182
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinbase/cds-mobile-visualization",
3
- "version": "3.4.0-beta.10",
3
+ "version": "3.4.0-beta.12",
4
4
  "description": "Coinbase Design System - Mobile Visualization Native",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,9 +36,9 @@
36
36
  "CHANGELOG"
37
37
  ],
38
38
  "peerDependencies": {
39
- "@coinbase/cds-common": "^8.35.1",
39
+ "@coinbase/cds-common": "^8.36.2",
40
40
  "@coinbase/cds-lottie-files": "^3.3.4",
41
- "@coinbase/cds-mobile": "^8.35.1",
41
+ "@coinbase/cds-mobile": "^8.36.2",
42
42
  "@coinbase/cds-utils": "^2.3.5",
43
43
  "@shopify/react-native-skia": "^1.12.4 || ^2.0.0",
44
44
  "react": "^18.3.1",
@@ -57,9 +57,9 @@
57
57
  "@babel/preset-env": "^7.28.0",
58
58
  "@babel/preset-react": "^7.27.1",
59
59
  "@babel/preset-typescript": "^7.27.1",
60
- "@coinbase/cds-common": "^8.35.1",
60
+ "@coinbase/cds-common": "^8.36.2",
61
61
  "@coinbase/cds-lottie-files": "^3.3.4",
62
- "@coinbase/cds-mobile": "^8.35.1",
62
+ "@coinbase/cds-mobile": "^8.36.2",
63
63
  "@coinbase/cds-utils": "^2.3.5",
64
64
  "@figma/code-connect": "^1.3.4",
65
65
  "@shopify/react-native-skia": "1.12.4",