@opendata-ai/openchart-engine 8.6.0 → 8.6.1

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
@@ -1021,6 +1021,10 @@ declare function legendGap(width: number): number;
1021
1021
  * - If `as` is a string: adds `as` with the bin start value.
1022
1022
  * - If `as` is [start, end]: adds both bin start and bin end fields.
1023
1023
  *
1024
+ * When `params.scaleType` is `'log'`, bins are log-spaced (equal width in log
1025
+ * space). This is auto-detected by the histogram mark when the x scale is
1026
+ * logarithmic.
1027
+ *
1024
1028
  * @param data - Input rows.
1025
1029
  * @param transform - Bin transform definition.
1026
1030
  * @returns New rows with binned field(s) added.
package/dist/index.js CHANGED
@@ -6544,8 +6544,15 @@ function expandEncodingSugar(spec, inheritedData) {
6544
6544
  const resolvedType = ch.type ?? (specData && specData.length > 0 ? inferFieldType(specData, field) : void 0);
6545
6545
  const emitInterval = channel === "x" && resolvedType === "quantitative" && markType === "bar";
6546
6546
  const endField = `${outputField}_end`;
6547
+ const chScale = ch.scale;
6548
+ let binDef;
6549
+ if (chScale?.type === "log") {
6550
+ binDef = ch.bin === true ? { scaleType: "log" } : { ...ch.bin, scaleType: "log" };
6551
+ } else {
6552
+ binDef = ch.bin === true ? true : ch.bin;
6553
+ }
6547
6554
  const binTransform = {
6548
- bin: ch.bin === true ? true : ch.bin,
6555
+ bin: binDef,
6549
6556
  field,
6550
6557
  as: emitInterval ? [outputField, endField] : outputField
6551
6558
  };
@@ -6914,7 +6921,14 @@ function expandHistogramMark(spec) {
6914
6921
  const encoding = { ...spec.encoding ?? {} };
6915
6922
  const x = { ...encoding.x ?? {} };
6916
6923
  if (x.bin == null) {
6917
- x.bin = { maxbins: typeof binCount === "number" ? binCount : HISTOGRAM_DEFAULT_MAXBINS };
6924
+ const binParams = {
6925
+ maxbins: typeof binCount === "number" ? binCount : HISTOGRAM_DEFAULT_MAXBINS
6926
+ };
6927
+ const xScale = x.scale;
6928
+ if (xScale?.type === "log") {
6929
+ binParams.scaleType = "log";
6930
+ }
6931
+ x.bin = binParams;
6918
6932
  }
6919
6933
  x.type = x.type ?? "quantitative";
6920
6934
  encoding.x = x;
@@ -19082,6 +19096,19 @@ function computeStep(extent3, maxbins, nice) {
19082
19096
  }
19083
19097
  return step;
19084
19098
  }
19099
+ function computeLogBinEdges(extent3, maxbins) {
19100
+ const lo = Math.max(extent3[0], Number.MIN_VALUE);
19101
+ const hi = Math.max(extent3[1], lo);
19102
+ const logMin = Math.log10(lo);
19103
+ const logMax = Math.log10(hi);
19104
+ const logSpan = logMax - logMin;
19105
+ if (logSpan === 0) return [lo, lo * 10];
19106
+ const edges = [];
19107
+ for (let i = 0; i <= maxbins; i++) {
19108
+ edges.push(10 ** (logMin + i / maxbins * logSpan));
19109
+ }
19110
+ return edges;
19111
+ }
19085
19112
  function runBin(data, transform) {
19086
19113
  const params = transform.bin === true ? {} : transform.bin;
19087
19114
  const maxbins = params.maxbins ?? 10;
@@ -19094,14 +19121,38 @@ function runBin(data, transform) {
19094
19121
  for (const row of data) {
19095
19122
  const v = Number(row[field]);
19096
19123
  if (Number.isFinite(v)) {
19124
+ if (params.scaleType === "log" && v <= 0) continue;
19097
19125
  if (v < min4) min4 = v;
19098
19126
  if (v > max4) max4 = v;
19099
19127
  }
19100
19128
  }
19101
19129
  extent3 = [min4 === Infinity ? 0 : min4, max4 === -Infinity ? 0 : max4];
19102
19130
  }
19103
- const step = params.step ?? computeStep(extent3, maxbins, nice);
19104
19131
  const [startAs, endAs] = Array.isArray(transform.as) ? transform.as : [transform.as, void 0];
19132
+ if (params.scaleType === "log") {
19133
+ const edges = computeLogBinEdges(extent3, maxbins);
19134
+ return data.map((row) => {
19135
+ const v = Number(row[field]);
19136
+ const newRow = { ...row };
19137
+ if (!Number.isFinite(v) || v <= 0) {
19138
+ newRow[startAs] = null;
19139
+ if (endAs) newRow[endAs] = null;
19140
+ } else {
19141
+ let lo = 0;
19142
+ let hi = edges.length - 2;
19143
+ while (lo < hi) {
19144
+ const mid = lo + hi + 1 >> 1;
19145
+ if (edges[mid] <= v) lo = mid;
19146
+ else hi = mid - 1;
19147
+ }
19148
+ const idx = Math.min(lo, edges.length - 2);
19149
+ newRow[startAs] = edges[idx];
19150
+ if (endAs) newRow[endAs] = edges[idx + 1];
19151
+ }
19152
+ return newRow;
19153
+ });
19154
+ }
19155
+ const step = params.step ?? computeStep(extent3, maxbins, nice);
19105
19156
  return data.map((row) => {
19106
19157
  const v = Number(row[field]);
19107
19158
  const newRow = { ...row };