svelteplot 0.7.1-pr-280.9 → 0.7.1-pr-280.11

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.
@@ -2,11 +2,12 @@
2
2
  * implementation based on science.js by Jason Davies
3
3
  */
4
4
  import type { TransformArg } from '../types';
5
- type DensityOptions = {
5
+ type Kernel = 'uniform' | 'triangular' | 'epanechnikov' | 'quartic' | 'triweight' | 'gaussian' | 'cosine' | ((u: number) => number);
6
+ type DensityOptions<T> = {
6
7
  /**
7
8
  * The kernel function to use for smoothing.
8
9
  */
9
- kernel?: 'uniform' | 'triangular' | 'epanechnikov' | 'quartic' | 'triweight' | 'gaussian' | 'cosine' | ((u: number) => number);
10
+ kernel?: Kernel;
10
11
  /**
11
12
  * The bandwidth to use for smoothing. Can be a fixed number or a function that computes the bandwidth based on the data.
12
13
  */
@@ -19,22 +20,18 @@ type DensityOptions = {
19
20
  *
20
21
  */
21
22
  trim?: boolean;
23
+ weight?: (d: T) => number;
22
24
  };
23
25
  /**
24
26
  * One-dimensional kernel density estimation
25
27
  */
26
- export declare function densityX<T>(args: TransformArg<T>, options: DensityOptions & {
28
+ export declare function densityX<T>(args: TransformArg<T>, options: DensityOptions<T> & {
27
29
  channel?: 'y' | 'y1' | 'y2';
28
30
  }): TransformArg<T>;
29
31
  /**
30
32
  * One-dimensional kernel density estimation
31
33
  */
32
- export declare function densityY<T>(args: TransformArg<T>, options: DensityOptions & {
34
+ export declare function densityY<T>(args: TransformArg<T>, options: DensityOptions<T> & {
33
35
  channel?: 'x' | 'x1' | 'x2';
34
36
  }): TransformArg<T>;
35
- /**
36
- * TODO: Two-dimensional kernel density estimation
37
- *
38
- */
39
- export declare function density<T>({ data, ...options }: TransformArg<T>, { kernel, bandwidth }: DensityOptions): TransformArg<T>;
40
37
  export {};
@@ -14,28 +14,7 @@ import { maybeInterval } from '../helpers/autoTicks';
14
14
  import { groupFacetsAndZ } from '../helpers/group';
15
15
  import isDataRecord from '../helpers/isDataRecord';
16
16
  import { resolveChannel } from '../helpers/resolve';
17
- // 2D Gaussian kernel function
18
- function gaussian2D(dx, dy, bwX, bwY) {
19
- return (Math.exp(-0.5 * ((dx * dx) / (bwX * bwX) + (dy * dy) / (bwY * bwY))) /
20
- (2 * Math.PI * bwX * bwY));
21
- }
22
- // kde2d function
23
- function kde2d(data, evalXs, evalYs, bwX, bwY) {
24
- const n = data.length;
25
- const density = [];
26
- for (let xi = 0; xi < evalXs.length; xi++) {
27
- for (let yi = 0; yi < evalYs.length; yi++) {
28
- let sum = 0;
29
- for (let i = 0; i < n; i++) {
30
- const dx = evalXs[xi] - data[i][0];
31
- const dy = evalYs[yi] - data[i][1];
32
- sum += gaussian2D(dx, dy, bwX, bwY);
33
- }
34
- // Store: [x, y, density]
35
- density.push([evalXs[xi], evalYs[yi], sum / n]);
36
- }
37
- }
38
- }
17
+ import { ORIGINAL_NAME_KEYS } from '../constants.js';
39
18
  // see https://github.com/jasondavies/science.js/blob/master/src/stats/kernel.js
40
19
  const KERNEL = {
41
20
  uniform(u) {
@@ -112,6 +91,7 @@ function bandwidthSilverman(x) {
112
91
  return lo * Math.pow(x.length, -0.2);
113
92
  }
114
93
  const VALUE = Symbol('value');
94
+ const WEIGHT = Symbol('weight');
115
95
  function roundToTerminating(x, sig = 2) {
116
96
  if (!isFinite(x) || x === 0)
117
97
  return x;
@@ -120,7 +100,7 @@ function roundToTerminating(x, sig = 2) {
120
100
  const factor = 10 ** decimals; // denominator 10^decimals → terminating decimal
121
101
  return Math.round(x * factor) / factor;
122
102
  }
123
- function density1d(independent, { data, ...channels }, options = {}) {
103
+ function density1d(independent, { data, weight, ...channels }, options = {}) {
124
104
  const densityChannel = independent === 'x' ? 'y' : 'x';
125
105
  const { kernel, bandwidth, interval, trim, channel } = {
126
106
  kernel: 'epanechnikov',
@@ -136,8 +116,15 @@ function density1d(independent, { data, ...channels }, options = {}) {
136
116
  const isRawDataArray = Array.isArray(data) && !isDataRecord(data[0]) && channels[independent] == null;
137
117
  // compute bandwidth before grouping
138
118
  const resolvedData = (isRawDataArray
139
- ? data.map((d) => ({ [VALUE]: d }))
140
- : data.map((d) => ({ [VALUE]: resolveChannel(independent, d, channels), ...d }))).filter((d) => isValid(d[VALUE]));
119
+ ? data.map((d) => ({
120
+ [VALUE]: d,
121
+ [WEIGHT]: typeof weight === 'function' ? weight(d) : 1
122
+ }))
123
+ : data.map((d) => ({
124
+ [VALUE]: resolveChannel(independent, d, channels),
125
+ [WEIGHT]: typeof weight === 'function' ? weight(d) : 1,
126
+ ...d
127
+ }))).filter((d) => isValid(d[VALUE]) && isValid(d[WEIGHT]) && d[WEIGHT] >= 0);
141
128
  const values = resolvedData.map((d) => d[VALUE]);
142
129
  // compute bandwidth from full data
143
130
  const bw = typeof bandwidth === 'function'
@@ -155,7 +142,8 @@ function density1d(independent, { data, ...channels }, options = {}) {
155
142
  // let maxX = -Infinity;
156
143
  const res = groupFacetsAndZ(resolvedData, channels, (items, groupProps) => {
157
144
  const values = items.map((d) => d[VALUE]);
158
- let kdeValues = kde1d(values, atValues, k, bw)
145
+ const weights = items.map((d) => d[WEIGHT]);
146
+ let kdeValues = kde1d(values, weights, atValues, k, bw)
159
147
  .filter(([x, density]) => x != null && !isNaN(density))
160
148
  .sort((a, b) => a[0] - b[0]);
161
149
  if (!trim) {
@@ -177,23 +165,22 @@ function density1d(independent, { data, ...channels }, options = {}) {
177
165
  [independent]: CHANNELS[independent],
178
166
  [channel]: CHANNELS[densityChannel],
179
167
  ...res,
168
+ [ORIGINAL_NAME_KEYS[densityChannel]]: 'Density',
169
+ [ORIGINAL_NAME_KEYS[independent]]: typeof channels[independent] === 'string' ? channels[independent] : undefined,
180
170
  sort: [{ channel: CHANNELS[independent], order: 'ascending' }],
181
171
  data: outData
182
172
  };
183
173
  }
184
- /**
185
- * TODO: Two-dimensional kernel density estimation
186
- *
187
- */
188
- export function density({ data, ...options }, { kernel, bandwidth }) { }
189
- function kde1d(values, atValues, kernel, bw) {
174
+ function kde1d(values, weights, atValues, kernel, bw) {
190
175
  const n = values.length;
176
+ const weightSum = weights.reduce((a, b) => a + b, 0);
191
177
  return atValues.map((x) => {
192
178
  let sum = 0;
193
179
  for (let i = 0; i < n; i++) {
194
- sum += kernel((x - values[i]) / bw);
180
+ const u = (x - values[i]) / bw;
181
+ sum += weights[i] * kernel(u);
195
182
  }
196
- return [x, sum / (n * bw)];
183
+ return [x, sum / (weightSum * bw)];
197
184
  });
198
185
  }
199
186
  function maybeKernel(kernel) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteplot",
3
- "version": "0.7.1-pr-280.9",
3
+ "version": "0.7.1-pr-280.11",
4
4
  "license": "ISC",
5
5
  "author": {
6
6
  "name": "Gregor Aisch",