-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsieve.js
171 lines (149 loc) · 5.35 KB
/
sieve.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/**
* sieve.js
*
* Utilities for storing grain size analysis (sieve) test data and calculating results
*
* class Sieve:
* - creates sieve objects that have properties size (of screen) and mass (of soil retained)
* - intended to be used by a SieveTest instance to populate the "stack" with "sieve" objects
*
* class SieveTest:
* - creates a "stack" of sieves and populates it with sieve objects from class Sieve
* - a "Pan" sieve is always created
* - usage:
* const test = new SieveTest({ sizes, sample, units });
* where:
* - "sizes" is an array containing numerical size of each sieve.
* - "sample" is an object containing data about the soil sample (class coming soon)
* - "units" is an optional type of unit e.g. metric or imperial. Defaults to metric.
*/
export class Sieve {
constructor(size, units = 'metric') {
// check that the given size is a positive number. One exception: allow a special case 'Pan'
if ((typeof size === 'number' && !Number.isNaN(size) && size > 0) || (size === 'Pan')) {
this.size = size;
} else {
throw new Error('Sieve constructor was called without a valid size');
}
this.mass = 0;
// Add units for numerical size/mass values.
if (units === 'imperial') {
this.sizeUnit = 'in';
this.massUnit = 'lb';
} else {
this.sizeUnit = 'mm';
this.massUnit = 'g';
}
}
retained(mass) {
/**
* Returns the mass retained on this sieve
* if given a number as input, also sets the mass retained (for now, it is still returned)
*/
if (typeof mass === 'number' && !Number.isNaN(mass)) {
this.mass = mass;
}
return this.mass;
}
}
export class SieveTest {
constructor(params = {}) {
/** Creates the SieveTest object. Accepts an object with the following properties:
*
* sizes: an array containing the sieve sizes for this test
* sample: an object containing sample information
* units: a string declaring the units - 'metric' (default) or 'imperial'
*
*/
const { sizes, units, sample } = params;
// start with some default values if a sample object was not provided
this.sampleData = sample || {
wetMass: 0,
dryMass: 0,
washedMass: 0,
};
const constructorStack = [];
if (Array.isArray(sizes)) {
// iterate through array of sieve sizes and add a Sieve object for each size
sizes.forEach((item) => {
// skip invalid non numeric input
if (typeof item === 'number' && !Number.isNaN(item)) {
const newSieve = new Sieve(item, units);
constructorStack.push(newSieve);
}
});
}
constructorStack.sort((a, b) => b.size - a.size);
// Add a default 'Pan' sieve to every stack
constructorStack.push(new Sieve('Pan', units));
this.stack = constructorStack;
}
index(size) {
/**
* Returns the position of the sieve of the given size in the sieve array (type Number)
*/
if (this.stack.length) {
return this.stack.findIndex(sieve => sieve.size === size);
}
throw new Error('There are no sieves in the SieveTest stack array. Add some sieves to the test');
}
addSieve(size) {
/**
* Adds a sieve of the given size into the correct (sorted) position in the sieve array.
*/
const newSieve = new Sieve(size);
const position = this.stack.findIndex(sieve => (sieve.size === 'Pan' || newSieve.size > sieve.size));
this.stack.splice(position, 0, newSieve);
}
removeSieve(size) {
/**
* Removes the specified sieve from the sieve array
*/
if (this.index(size) !== -1) {
// use splice to delete the specified sieve
this.stack.splice(this.index(size), 1);
} else {
throw new Error(`Sieve with size ${size} not found`);
}
}
sieve(size) {
/**
* returns the Sieve object of the specified size
*/
return this.stack.find(sieve => sieve.size === size);
}
passing() {
/**
* Computes the percent passing (the amount of soil that was able to pass through a sieve)
* for each sieve in the sieve array.
*
* To calculate this, iterate through the sieves (starting at the top) and keep track of
* the cumulative total mass down to that point in the stack. The percent passing at any
* point is the total mass of the sample minus the cumulative mass retained.
*/
const { stack } = this;
const { dryMass } = this.sampleData;
// make sure dryMass has been inputted and that sieves exist in the sieve array
if (dryMass && stack.length) {
const result = [];
let cumulativeMass = 0;
for (let i = 0; i < stack.length; i += 1) {
const sieve = stack[i];
// this calculates how much soil passed through the sieve:
const passing = dryMass - sieve.mass - cumulativeMass;
// and how much soil has been retained by sieves so far (including current sieve):
cumulativeMass += sieve.mass;
// Push the results from all the sieves (do not include the pan) into the result array.
if (sieve.size !== 'Pan') {
result.push({
size: sieve.size,
mass: sieve.mass,
percentPassing: ((passing / dryMass) * 100).toFixed(1),
});
}
}
return result;
}
throw new Error('passing() requires both dryMass and sieve properties to be present');
}
}