@oliasoft-open-source/charts-library 5.10.0-beta-2 → 5.10.0-beta-4

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.
package/dist/index.d.ts CHANGED
@@ -1116,6 +1116,13 @@ export declare type UnusedParameter = unknown;
1116
1116
  export { }
1117
1117
 
1118
1118
 
1119
+ declare module 'chart.js' {
1120
+ interface PluginOptionsByType<TType extends ChartType> {
1121
+ annotationDraggerPlugin?: AnnotationDraggerPluginOptions;
1122
+ }
1123
+ }
1124
+
1125
+
1119
1126
  /**
1120
1127
  * Vertical Markers Plugin
1121
1128
  *
@@ -1176,10 +1183,3 @@ declare module 'chart.js' {
1176
1183
  };
1177
1184
  }
1178
1185
  }
1179
-
1180
-
1181
- declare module 'chart.js' {
1182
- interface PluginOptionsByType<TType extends ChartType> {
1183
- annotationDraggerPlugin?: AnnotationDraggerPluginOptions;
1184
- }
1185
- }
package/dist/index.js CHANGED
@@ -20889,13 +20889,18 @@ var DEFAULT_VERTICAL_OFFSET = -18;
20889
20889
  var DEFAULT_ROW_GAP = 22;
20890
20890
  var DEFAULT_ITEM_GAP = 8;
20891
20891
  var DEFAULT_OVERLAP_ONLY_ITEM_GAP = 14;
20892
- var DEFAULT_SIDE_SPREAD_STEP = 12;
20893
- var DEFAULT_SIDE_SPREAD_MAX = 48;
20892
+ var DEFAULT_LANE_GAP = 12;
20894
20893
  var clamp$1 = (value, min, max) => {
20895
20894
  if (Number.isNaN(value)) return min;
20896
20895
  if (min > max) return min;
20897
20896
  return Math.min(Math.max(value, min), max);
20898
20897
  };
20898
+ var clampRelaxed = (value, min, max) => {
20899
+ if (min <= max) return clamp$1(value, min, max);
20900
+ if (value < max) return max;
20901
+ if (value > min) return min;
20902
+ return value;
20903
+ };
20899
20904
  var getNumericValue = (value, fallback = 0) => {
20900
20905
  return typeof value === "number" ? value : fallback;
20901
20906
  };
@@ -21099,15 +21104,6 @@ var getOverlappingCalloutIds = (chart) => {
21099
21104
  });
21100
21105
  return overlappingIds;
21101
21106
  };
21102
- var getOccupiedOriginalLabelRects = (chart, area, overlappingIds) => {
21103
- return ((chart?.options)?.annotations?.annotationsData ?? []).flatMap((annotation, index) => {
21104
- const calloutCfg = getCalloutCfg(annotation);
21105
- const id = `callout-annotation-${index}`;
21106
- if (!calloutCfg?.enabled || !calloutCfg?.onlyWhenOverlapping) return [];
21107
- if (overlappingIds.has(id)) return [];
21108
- return [getOriginalLabelRect(chart, area, annotation, index)];
21109
- });
21110
- };
21111
21107
  var isCalloutOverlapping = (ctx, refAnnotation, index) => {
21112
21108
  const chart = getChartFromCtx$1(ctx);
21113
21109
  if (!getCalloutCfg(refAnnotation)?.onlyWhenOverlapping) return true;
@@ -21140,7 +21136,6 @@ var getCalloutItems = (chart, area) => {
21140
21136
  const calloutCfg = getCalloutCfg(annotation);
21141
21137
  const id = `callout-annotation-${index}`;
21142
21138
  if (!calloutCfg?.enabled) return [];
21143
- if (calloutCfg?.onlyWhenOverlapping && !overlappingIds.has(id)) return [];
21144
21139
  const size = estimateLabelSize(chart, annotation, calloutCfg);
21145
21140
  const xScale = getXScale(chart, getAxisId(annotation, "x"));
21146
21141
  const yScale = getYScale(chart, getAxisId(annotation, "y"));
@@ -21162,6 +21157,7 @@ var getCalloutItems = (chart, area) => {
21162
21157
  width: size.width,
21163
21158
  height: size.height,
21164
21159
  isFixed: hasStoredX && hasStoredY,
21160
+ isOverlapping: overlappingIds.has(id),
21165
21161
  overlapOnly: Boolean(calloutCfg?.onlyWhenOverlapping),
21166
21162
  minX: area.left + margin + size.width / 2,
21167
21163
  maxX: area.right - margin - size.width / 2,
@@ -21192,7 +21188,79 @@ var getLayoutGap = (item) => {
21192
21188
  var getLayoutOrderY = (item) => {
21193
21189
  return item.isFixed ? item.preferredY : item.anchorY;
21194
21190
  };
21191
+ var intervalsOverlap = (top, bottom, otherTop, otherBottom) => {
21192
+ return !(bottom <= otherTop || top >= otherBottom);
21193
+ };
21194
+ var assignLabelColumns = (items, layout) => {
21195
+ const columns = [];
21196
+ const hasPrimaryColumn = items.some((item) => {
21197
+ return !item.overlapOnly || !item.isOverlapping;
21198
+ });
21199
+ [...items].sort((left, right) => getLayoutOrderY(left) - getLayoutOrderY(right)).forEach((item) => {
21200
+ const current = layout.get(item.id);
21201
+ if (!current) return;
21202
+ const top = current.yValue - item.height / 2 - getLayoutGap(item) / 2;
21203
+ const bottom = current.yValue + item.height / 2 + getLayoutGap(item) / 2;
21204
+ let columnIndex = hasPrimaryColumn && item.overlapOnly && item.isOverlapping ? 1 : 0;
21205
+ while (true) {
21206
+ if (!(columns[columnIndex] ?? []).some((placed) => {
21207
+ return intervalsOverlap(top, bottom, placed.top, placed.bottom);
21208
+ })) {
21209
+ if (!columns[columnIndex]) columns[columnIndex] = [];
21210
+ columns[columnIndex].push({
21211
+ id: item.id,
21212
+ top,
21213
+ bottom,
21214
+ width: item.width
21215
+ });
21216
+ break;
21217
+ }
21218
+ columnIndex += 1;
21219
+ }
21220
+ });
21221
+ return columns;
21222
+ };
21223
+ var applyLaneLayout = (items, layout, occupiedRects) => {
21224
+ if (items.length <= 1) return;
21225
+ const side = items[0]?.side;
21226
+ const columns = assignLabelColumns(items, layout);
21227
+ const getCurrentRect = (item) => {
21228
+ const centerX = layout.get(item.id)?.xValue ?? item.preferredX;
21229
+ return {
21230
+ left: centerX - item.width / 2,
21231
+ right: centerX + item.width / 2
21232
+ };
21233
+ };
21234
+ const referenceItems = items.filter((item) => {
21235
+ return columns[0]?.some((placed) => placed.id === item.id);
21236
+ });
21237
+ const referenceRects = referenceItems.length ? referenceItems : items;
21238
+ const baseEdge = side === "right" ? occupiedRects.length ? Math.min(...occupiedRects.map((rect) => rect.left)) - DEFAULT_LANE_GAP : Math.min(...referenceRects.map((item) => getCurrentRect(item).left)) : occupiedRects.length ? Math.max(...occupiedRects.map((rect) => rect.right)) + DEFAULT_LANE_GAP : Math.max(...referenceRects.map((item) => getCurrentRect(item).right));
21239
+ columns.forEach((column, columnIndex) => {
21240
+ const columnWidth = Math.max(...column.map((placed) => placed.width));
21241
+ column?.forEach((placed) => {
21242
+ const item = items.find((candidate) => candidate.id === placed.id);
21243
+ const current = layout.get(placed.id);
21244
+ if (!item || !current) return;
21245
+ const xValue = side === "right" ? baseEdge - columnIndex * (columnWidth + DEFAULT_LANE_GAP) - item.width / 2 : baseEdge + columnIndex * (columnWidth + DEFAULT_LANE_GAP) + item.width / 2;
21246
+ layout.set(placed.id, {
21247
+ ...current,
21248
+ xValue: clamp$1(xValue, item.minX, item.maxX)
21249
+ });
21250
+ });
21251
+ });
21252
+ };
21253
+ var getOrderBounds = (item, occupiedRects) => {
21254
+ const previousRect = [...occupiedRects].filter((rect) => rect.centerY <= item.anchorY).at(-1);
21255
+ const nextRect = occupiedRects.find((rect) => rect.centerY >= item.anchorY);
21256
+ const gap = getLayoutGap(item);
21257
+ return {
21258
+ minCenter: previousRect ? previousRect.bottom + gap + item.height / 2 : item.minY,
21259
+ maxCenter: nextRect ? nextRect.top - gap - item.height / 2 : item.maxY
21260
+ };
21261
+ };
21195
21262
  var layoutSide = (items, occupiedRects = []) => {
21263
+ const sortedOccupiedRects = [...occupiedRects].sort((left, right) => left.centerY - right.centerY);
21196
21264
  const sorted = [...items].sort((left, right) => getLayoutOrderY(left) - getLayoutOrderY(right));
21197
21265
  const result = /* @__PURE__ */ new Map();
21198
21266
  if (!sorted.filter((item) => !item.isFixed).length) {
@@ -21223,12 +21291,16 @@ var layoutSide = (items, occupiedRects = []) => {
21223
21291
  previousBottom = yValue + item.height / 2;
21224
21292
  return;
21225
21293
  }
21226
- const minCenter = Math.max(item.minY, previousBottom + item.height / 2 + getLayoutGap(item));
21227
- let yValue = clamp$1(item.preferredY, minCenter, item.maxY);
21228
- let overlappingRect = overlapsOccupiedRect(yValue, item.height, occupiedRects);
21294
+ const orderBounds = getOrderBounds(item, sortedOccupiedRects);
21295
+ const maxCenter = Math.min(item.maxY, orderBounds.maxCenter);
21296
+ const minCenter = Math.max(item.minY, orderBounds.minCenter, previousBottom + item.height / 2 + getLayoutGap(item));
21297
+ let yValue = clampRelaxed(item.preferredY, minCenter, maxCenter);
21298
+ let overlappingRect = overlapsOccupiedRect(yValue, item.height, sortedOccupiedRects);
21229
21299
  while (overlappingRect) {
21230
- yValue = clamp$1(overlappingRect.bottom + getLayoutGap(item) + item.height / 2, item.minY, item.maxY);
21231
- overlappingRect = overlapsOccupiedRect(yValue, item.height, occupiedRects);
21300
+ const nextCandidate = overlappingRect.bottom + getLayoutGap(item) + item.height / 2;
21301
+ if (nextCandidate === yValue) break;
21302
+ yValue = clampRelaxed(nextCandidate, minCenter, maxCenter);
21303
+ overlappingRect = overlapsOccupiedRect(yValue, item.height, sortedOccupiedRects);
21232
21304
  }
21233
21305
  result.set(item.id, {
21234
21306
  xValue: item.preferredX,
@@ -21244,54 +21316,67 @@ var layoutSide = (items, occupiedRects = []) => {
21244
21316
  nextTop = current.yValue - item.height / 2;
21245
21317
  return;
21246
21318
  }
21247
- const maxCenter = Math.min(item.maxY, nextTop - item.height / 2 - getLayoutGap(item));
21248
- const yValue = clamp$1(current.yValue, item.minY, maxCenter);
21319
+ const orderBounds = getOrderBounds(item, sortedOccupiedRects);
21320
+ const maxCenter = Math.min(item.maxY, orderBounds.maxCenter, nextTop - item.height / 2 - getLayoutGap(item));
21321
+ const minCenter = Math.max(item.minY, orderBounds.minCenter);
21322
+ const yValue = clampRelaxed(current.yValue, minCenter, maxCenter);
21249
21323
  result.set(item.id, {
21250
21324
  ...current,
21251
21325
  yValue
21252
21326
  });
21253
21327
  nextTop = yValue - item.height / 2;
21254
21328
  });
21255
- const overlapOnlyMovable = sorted.filter((item) => {
21256
- return item.overlapOnly && !item.isFixed;
21329
+ previousBottom = Number.NEGATIVE_INFINITY;
21330
+ sorted.forEach((item) => {
21331
+ const current = result.get(item.id);
21332
+ if (!current) return;
21333
+ if (item.isFixed) {
21334
+ previousBottom = current.yValue + item.height / 2;
21335
+ return;
21336
+ }
21337
+ const orderBounds = getOrderBounds(item, sortedOccupiedRects);
21338
+ const maxCenter = Math.min(item.maxY, orderBounds.maxCenter);
21339
+ const minCenter = Math.max(item.minY, orderBounds.minCenter, previousBottom + item.height / 2 + getLayoutGap(item));
21340
+ const yValue = clampRelaxed(current.yValue, minCenter, maxCenter);
21341
+ result.set(item.id, {
21342
+ ...current,
21343
+ yValue
21344
+ });
21345
+ previousBottom = yValue + item.height / 2;
21257
21346
  });
21258
- if (overlapOnlyMovable.length > 1) {
21259
- const sortedOverlapOnlyMovable = [...overlapOnlyMovable].sort((left, right) => getLayoutOrderY(left) - getLayoutOrderY(right));
21260
- const sharedOuterEdge = sortedOverlapOnlyMovable[0]?.side === "right" ? Math.max(...sortedOverlapOnlyMovable.map((item) => {
21261
- return (result.get(item.id)?.xValue ?? item.preferredX) + item.width / 2;
21262
- })) : Math.min(...sortedOverlapOnlyMovable.map((item) => {
21263
- return (result.get(item.id)?.xValue ?? item.preferredX) - item.width / 2;
21264
- }));
21265
- sortedOverlapOnlyMovable.forEach((item, index) => {
21266
- const current = result.get(item.id);
21267
- if (!current) return;
21268
- const spreadOffset = Math.min(index * DEFAULT_SIDE_SPREAD_STEP, DEFAULT_SIDE_SPREAD_MAX);
21269
- const direction = item.side === "right" ? -1 : 1;
21270
- const alignedXValue = item.side === "right" ? sharedOuterEdge - item.width / 2 : sharedOuterEdge + item.width / 2;
21347
+ if (sorted.some((item) => item.overlapOnly)) {
21348
+ const untouchedPrimary = sorted.filter((item) => {
21349
+ return !item.isFixed && item.overlapOnly && !item.isOverlapping;
21350
+ });
21351
+ const secondaryMovable = sorted.filter((item) => {
21352
+ return !item.isFixed && item.overlapOnly && item.isOverlapping;
21353
+ });
21354
+ untouchedPrimary.forEach((item) => {
21271
21355
  result.set(item.id, {
21272
- ...current,
21273
- xValue: clamp$1(alignedXValue + direction * spreadOffset, item.minX, item.maxX)
21356
+ xValue: item.preferredX,
21357
+ yValue: clamp$1(item.preferredY, item.minY, item.maxY)
21274
21358
  });
21275
21359
  });
21360
+ if (secondaryMovable.length) applyLaneLayout(secondaryMovable, result, untouchedPrimary.map((item) => {
21361
+ const current = result.get(item.id);
21362
+ const centerX = current?.xValue ?? item.preferredX;
21363
+ const centerY = current?.yValue ?? item.preferredY;
21364
+ return {
21365
+ top: centerY - item.height / 2,
21366
+ bottom: centerY + item.height / 2,
21367
+ centerY,
21368
+ left: centerX - item.width / 2,
21369
+ right: centerX + item.width / 2
21370
+ };
21371
+ }));
21276
21372
  return result;
21277
21373
  }
21278
- sorted.forEach((item, index) => {
21279
- const current = result.get(item.id);
21280
- if (!current || item.isFixed) return;
21281
- const spreadOffset = Math.min(index * DEFAULT_SIDE_SPREAD_STEP, DEFAULT_SIDE_SPREAD_MAX);
21282
- const direction = item.side === "right" ? -1 : 1;
21283
- const xValue = clamp$1(current.xValue + direction * spreadOffset, item.minX, item.maxX);
21284
- result.set(item.id, {
21285
- ...current,
21286
- xValue
21287
- });
21288
- });
21374
+ applyLaneLayout(sorted.filter((item) => !item.isFixed), result, []);
21289
21375
  return result;
21290
21376
  };
21291
21377
  var getCalloutLayoutPixels = (chart) => {
21292
21378
  const area = chart?.chartArea;
21293
21379
  if (!chart || !area) return /* @__PURE__ */ new Map();
21294
- const occupiedRects = getOccupiedOriginalLabelRects(chart, area, getOverlappingCalloutIds(chart));
21295
21380
  const grouped = getCalloutItems(chart, area).map((item) => {
21296
21381
  const xValue = clamp$1(item.preferredX, item.minX, item.maxX);
21297
21382
  return {
@@ -21305,14 +21390,8 @@ var getCalloutLayoutPixels = (chart) => {
21305
21390
  left: [],
21306
21391
  right: []
21307
21392
  });
21308
- const leftLayout = layoutSide(grouped.left, occupiedRects.filter((rect) => rect.side === "left").map((rect) => ({
21309
- top: rect.top,
21310
- bottom: rect.bottom
21311
- })));
21312
- const rightLayout = layoutSide(grouped.right, occupiedRects.filter((rect) => rect.side === "right").map((rect) => ({
21313
- top: rect.top,
21314
- bottom: rect.bottom
21315
- })));
21393
+ const leftLayout = layoutSide(grouped.left, []);
21394
+ const rightLayout = layoutSide(grouped.right, []);
21316
21395
  return new Map([...leftLayout.entries(), ...rightLayout.entries()]);
21317
21396
  };
21318
21397
  var getCalloutLayout = (ctx, refAnnotation, index) => {
@@ -21686,11 +21765,7 @@ var getCalloutAnnotation = (refAnnotation, index) => {
21686
21765
  ...baseRest,
21687
21766
  label: {
21688
21767
  ...label,
21689
- display: (ctx) => {
21690
- if (!(refAnnotation.display ?? true)) return false;
21691
- if (!calloutCfg?.onlyWhenOverlapping) return false;
21692
- return !isCalloutOverlapping(ctx, refAnnotation, index);
21693
- }
21768
+ display: () => false
21694
21769
  }
21695
21770
  };
21696
21771
  const color = calloutCfg?.color ?? "hsl(60, 10.34482759%, 12.5%)";
@@ -21722,7 +21797,7 @@ var getCalloutAnnotation = (refAnnotation, index) => {
21722
21797
  display: (ctx) => {
21723
21798
  if (!(refAnnotation.display ?? true)) return false;
21724
21799
  if (!isCalloutAnchorInChartArea(ctx, refAnnotation, calloutCfg.margin ?? 0)) return false;
21725
- return isCalloutOverlapping(ctx, refAnnotation, index);
21800
+ return true;
21726
21801
  },
21727
21802
  xValue: (ctx) => {
21728
21803
  const persistenceId = getChartFromCtx(ctx)?.options?.persistenceId;
@@ -21755,7 +21830,10 @@ var getCalloutAnnotation = (refAnnotation, index) => {
21755
21830
  return onCalloutDragEnd(coords, refAnnotation);
21756
21831
  } : void 0,
21757
21832
  calloutConnector: {
21758
- enabled: true,
21833
+ enabled: ({ chart }) => {
21834
+ if (!calloutCfg?.onlyWhenOverlapping) return true;
21835
+ return isCalloutOverlapping({ chart }, refAnnotation, index);
21836
+ },
21759
21837
  fromId: baseId,
21760
21838
  strokeStyle,
21761
21839
  lineWidth,
@@ -22724,11 +22802,17 @@ var calloutConnectorPlugin = {
22724
22802
  if (!raw) return;
22725
22803
  (Array.isArray(raw) ? raw : Object.values(raw)).forEach((opt) => {
22726
22804
  const connector = opt?.calloutConnector;
22727
- if (!connector || !connector.enabled || !opt?.id) return;
22805
+ if (!connector || !opt?.id) return;
22728
22806
  const labelEl = elements.find((el) => el.options && el.options.id === opt.id);
22729
22807
  const fromEl = elements.find((el) => el.options && el.options.id === connector.fromId);
22730
22808
  if (!labelEl || !fromEl) return;
22731
22809
  if (labelEl.options?.display === false) return;
22810
+ if (!(typeof connector.enabled === "function" ? connector.enabled({
22811
+ chart,
22812
+ labelEl,
22813
+ fromEl,
22814
+ option: opt
22815
+ }) : connector.enabled !== false)) return;
22732
22816
  const { startX, startY, endX, endY, tickStartX, tickStartY, tickEndX, tickEndY } = computeConnectorPoints(fromEl, labelEl, connector) ?? {};
22733
22817
  const ctx = chart?.ctx;
22734
22818
  const strokeStyle = connector?.strokeStyle ?? "rgba(120, 126, 138, 0.9)";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oliasoft-open-source/charts-library",
3
- "version": "5.10.0-beta-2",
3
+ "version": "5.10.0-beta-4",
4
4
  "description": "React Chart Library (based on Chart.js and react-chart-js-2)",
5
5
  "homepage": "https://gitlab.com/oliasoft-open-source/charts-library",
6
6
  "bugs": {