Skip to content

Commit

Permalink
feat: sample function (#1829)
Browse files Browse the repository at this point in the history
* feat: sample function
---------

Co-authored-by: xuying.xu <[email protected]>
  • Loading branch information
tangying1027 and xuying.xu authored Aug 24, 2023
1 parent 8fa1e63 commit 57ef157
Show file tree
Hide file tree
Showing 13 changed files with 422 additions and 0 deletions.
83 changes: 83 additions & 0 deletions packages/f2-algorithm/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# lock
package-lock.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

.DS_Store

# npmignore - content above this line is automatically generated and modifications may be omitted
# see npmjs.com/npmignore for more details.
test

*.sw*
*.un~
.idea
bin
demos
docs
temp
webpack-dev.config.js
webpack.config.js
examples
site
gatsby-browser.js
gatsby-config.js
.cache
public
1 change: 1 addition & 0 deletions packages/f2-algorithm/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Change Log
22 changes: 22 additions & 0 deletions packages/f2-algorithm/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
MIT LICENSE

Copyright (c) 2015-present Alipay.com, https://www.alipay.com/

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 changes: 21 additions & 0 deletions packages/f2-algorithm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
F2 的 算法库

常用算法扩展包

## Usage

```jsx
import dataSample from '@antv/f2-algorithm';

const sampleData = dataSample({
data,
sampling: 'nearest',
rate: 7,
});

<Canvas pixelRatio={1}>
<Chart data={sampleData}>
<Line x="date" y="value" />
</Chart>
</Canvas>;
```
38 changes: 38 additions & 0 deletions packages/f2-algorithm/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "f2-algorithm",
"version": "5.0.0",
"description": "F2 algorithm extension",
"main": "lib/index.js",
"module": "es/index.js",
"types": "es/index.d.ts",
"sideEffects": false,
"keywords": [
"antv",
"f2",
"chart",
"charts",
"mobile",
"visualization",
"react",
"sample"
],
"dependencies": {
"@babel/runtime": "^7.12.5",
"tslib": "^2.3.1"
},
"devDependencies": {
"@antv/f2": "^5.0.27",
"@antv/f-test-utils": "^1.0.1",
"jest-mock-random": "~1.1.1"
},
"homepage": "https://f2.antv.vision/zh/",
"author": "https://github.com/orgs/antvis/people",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/antvis/f2"
},
"bugs": {
"url": "https://github.com/antvis/f2/issues"
}
}
4 changes: 4 additions & 0 deletions packages/f2-algorithm/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import lttbDownSample from './lttbDownSample';
import rateDownSample from './rateDownSample';

export { lttbDownSample, rateDownSample };
81 changes: 81 additions & 0 deletions packages/f2-algorithm/src/lttbDownSample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Large data down sampling using largest-triangle-three-buckets.
* A B C
*
* 桶:包含数据子区间的有序集。
* @param {string} data
* @param {number} rate
* * @param {number} dimension
*/

export interface OptionsProps {
/*周期 */
rate: number;
dimension: string;
}

export default function lttbDownSample(data, options?: OptionsProps) {
const { rate = 5, dimension = 'value' } = options;
const len = data.length;
const targetCount = len / rate;
if (targetCount >= len || targetCount === 0) {
return data;
}

const sampled = [];
let sampledIndex = 0;
// Bucket size
const bucketSize = Math.floor((len - 2) / (targetCount - 2));

// A is the first point in the triangle
let a = 0,
maxAreaPoint,
maxArea,
area,
// B Bucket
nextA;

sampled[sampledIndex++] = data[a];

for (let i = 1; i < len - 1; i += bucketSize) {
// Calculate point average for C bucket
let avgRangeStart = Math.min(i + bucketSize, len - 1);
const avgRangeEnd = Math.min(i + 2 * bucketSize, len);

const avgX = (avgRangeEnd + avgRangeStart) / 2;
let avgY = 0;
const avgLength = avgRangeEnd - avgRangeStart;
for (; avgRangeStart < avgRangeEnd; avgRangeStart++) {
avgY += Number(data[avgRangeStart][dimension]);
}
// 桶的平均数
avgY /= avgLength;

// Get the range for B bucket
const rangeTo = Math.min(i + bucketSize, len);

// Point a
const pointA_x = i - 1;
const pointA_y = Number(data[a][dimension]);
maxArea = area = -1;

for (let rangeOffs = i; rangeOffs < rangeTo; rangeOffs++) {
// Calculate triangle area over three buckets
area =
Math.abs(
(pointA_x - avgX) * (Number(data[rangeOffs][dimension]) - pointA_y) -
(pointA_x - rangeOffs) * (avgY - pointA_y)
) * 0.5;
if (area > maxArea) {
maxArea = area;
maxAreaPoint = data[rangeOffs];
nextA = rangeOffs;
}
}

sampled[sampledIndex++] = maxAreaPoint;
a = nextA; // T
}
sampled[sampledIndex++] = data[len - 1];
return sampled;
}
76 changes: 76 additions & 0 deletions packages/f2-algorithm/src/rateDownSample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { isString, isFunction } from '@antv/util';

/**
* data down sampling
* 常见统计采样,最大最小固定点等
* @param {string} data
* @param {number} targetCount
* @param {string} sampler
*/

function downSample(data, rate, sampler, dimension) {
const sampled = [];
let sampledIndex = 0;
const len = data.length;
let frameSize = Math.floor(rate);

for (let i = 0; i < len; i += frameSize) {
// Last frame
frameSize = frameSize > len - i ? len - i : frameSize;
const frameValues = [];

for (let k = 0; k < frameSize; k++) {
frameValues[k] = data[k + i];
}

const value = sampler(frameValues, dimension);
sampled[sampledIndex++] = value;
}

return sampled;
}

const samplers = {
max: (frame, dimension) => {
const max = -Infinity;
let maxData;
for (let i = 0; i < frame.length; i++) {
frame[i][dimension] > max && (maxData = frame[i]);
}
return maxData || NaN;
},
min: (frame, dimension) => {
const min = Infinity;
let minData;
for (let i = 0; i < frame.length; i++) {
frame[i][dimension] < min && (minData = frame[i]);
}
return minData || NaN;
},
// TODO 中位数 median
nearest: (frame) => {
return frame[0];
},
};

export interface OptionsProps {
sampling: 'nearest' | 'max' | 'min' | Function;
/* 周期 */
rate: number;
dimension: string;
}

export default function rateDownSample(data, options?: OptionsProps) {
const { sampling = 'nearest', rate = 5, dimension = 'value' } = options;
let sampler;
if (isFinite(rate) && rate > 1) {
if (isString(sampling)) {
sampler = samplers[sampling];
} else if (isFunction(sampling)) {
sampler = sampling;
}

return downSample(data, rate, sampler, dimension);
}
return data;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 57ef157

Please sign in to comment.