index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. const {
  2. formatMagnitudeNumber,
  3. formatScaledValue,
  4. getOption,
  5. normalizeIndex,
  6. parsePositiveNumber,
  7. selectBestUnit
  8. } = require('../calculator-helpers.js')
  9. const TWO_PI = 2 * Math.PI
  10. const DIAGRAM_COMPONENT_SRC = {
  11. c: '/assets/filter-diagram/capacitor-h.svg',
  12. l: '/assets/filter-diagram/inductor-h.svg',
  13. r: '/assets/filter-diagram/resistor-h.svg'
  14. }
  15. const NETWORK_OPTIONS = [
  16. { key: 'rc', label: 'RC' },
  17. { key: 'rl', label: 'RL' }
  18. ]
  19. const RESPONSE_OPTIONS = [
  20. { key: 'lowpass', label: '低通' },
  21. { key: 'highpass', label: '高通' }
  22. ]
  23. const RESISTANCE_UNIT_OPTIONS = [
  24. { label: 'mΩ', factor: 1e-3 },
  25. { label: 'Ω', factor: 1 },
  26. { label: 'kΩ', factor: 1e3 },
  27. { label: 'MΩ', factor: 1e6 },
  28. { label: 'GΩ', factor: 1e9 }
  29. ]
  30. const CAPACITANCE_UNIT_OPTIONS = [
  31. { label: 'pF', factor: 1e-12 },
  32. { label: 'nF', factor: 1e-9 },
  33. { label: 'μF', factor: 1e-6 },
  34. { label: 'mF', factor: 1e-3 },
  35. { label: 'F', factor: 1 }
  36. ]
  37. const INDUCTANCE_UNIT_OPTIONS = [
  38. { label: 'pH', factor: 1e-12 },
  39. { label: 'nH', factor: 1e-9 },
  40. { label: 'μH', factor: 1e-6 },
  41. { label: 'mH', factor: 1e-3 },
  42. { label: 'H', factor: 1 },
  43. { label: 'kH', factor: 1e3 }
  44. ]
  45. const FREQUENCY_UNIT_OPTIONS = [
  46. { label: 'Hz', factor: 1 },
  47. { label: 'kHz', factor: 1e3 },
  48. { label: 'MHz', factor: 1e6 },
  49. { label: 'GHz', factor: 1e9 },
  50. { label: 'THz', factor: 1e12 }
  51. ]
  52. function getReactiveUnitOptions(networkKey) {
  53. return networkKey === 'rl' ? INDUCTANCE_UNIT_OPTIONS : CAPACITANCE_UNIT_OPTIONS
  54. }
  55. function getDiagramComponentSrc(componentKey) {
  56. return DIAGRAM_COMPONENT_SRC[componentKey] || DIAGRAM_COMPONENT_SRC.r
  57. }
  58. function getDiagramComponentLabel(componentKey) {
  59. return String(componentKey || '').toUpperCase()
  60. }
  61. function getDiagramComponents(networkKey, responseKey) {
  62. if (networkKey === 'rc' && responseKey === 'highpass') {
  63. return { series: 'c', shunt: 'r' }
  64. }
  65. if (networkKey === 'rl' && responseKey === 'lowpass') {
  66. return { series: 'l', shunt: 'r' }
  67. }
  68. if (networkKey === 'rl' && responseKey === 'highpass') {
  69. return { series: 'r', shunt: 'l' }
  70. }
  71. return { series: 'r', shunt: 'c' }
  72. }
  73. function calculateMissingValue(networkKey, values) {
  74. const hasResistance = Number.isFinite(values.resistance)
  75. const hasReactive = Number.isFinite(values.reactive)
  76. const hasFrequency = Number.isFinite(values.frequency)
  77. const validCount = [hasResistance, hasReactive, hasFrequency].filter(Boolean).length
  78. if (validCount < 2) {
  79. return {
  80. computedKey: '',
  81. errorText: ''
  82. }
  83. }
  84. if (validCount > 2) {
  85. return {
  86. computedKey: '',
  87. errorText: '保留两项,清空一项用于计算'
  88. }
  89. }
  90. if (networkKey === 'rl') {
  91. if (!hasResistance) {
  92. return {
  93. computedKey: 'resistance',
  94. value: TWO_PI * values.frequency * values.reactive
  95. }
  96. }
  97. if (!hasReactive) {
  98. return {
  99. computedKey: 'reactive',
  100. value: values.resistance / (TWO_PI * values.frequency)
  101. }
  102. }
  103. return {
  104. computedKey: 'frequency',
  105. value: values.resistance / (TWO_PI * values.reactive)
  106. }
  107. }
  108. if (!hasResistance) {
  109. return {
  110. computedKey: 'resistance',
  111. value: 1 / (TWO_PI * values.frequency * values.reactive)
  112. }
  113. }
  114. if (!hasReactive) {
  115. return {
  116. computedKey: 'reactive',
  117. value: 1 / (TWO_PI * values.frequency * values.resistance)
  118. }
  119. }
  120. return {
  121. computedKey: 'frequency',
  122. value: 1 / (TWO_PI * values.resistance * values.reactive)
  123. }
  124. }
  125. function buildState(source = {}) {
  126. const networkIndex = normalizeIndex(source.filterNetworkIndex, NETWORK_OPTIONS, 0)
  127. const responseIndex = normalizeIndex(source.filterResponseIndex, RESPONSE_OPTIONS, 0)
  128. const network = getOption(NETWORK_OPTIONS, networkIndex)
  129. const response = getOption(RESPONSE_OPTIONS, responseIndex)
  130. const reactiveUnitOptions = getReactiveUnitOptions(network.key)
  131. const resistanceUnitIndex = normalizeIndex(source.filterResistanceUnitIndex, RESISTANCE_UNIT_OPTIONS, 1)
  132. const capacitanceUnitIndex = normalizeIndex(source.filterCapacitanceUnitIndex, CAPACITANCE_UNIT_OPTIONS, 1)
  133. const inductanceUnitIndex = normalizeIndex(source.filterInductanceUnitIndex, INDUCTANCE_UNIT_OPTIONS, 3)
  134. const frequencyUnitIndex = normalizeIndex(source.filterFrequencyUnitIndex, FREQUENCY_UNIT_OPTIONS, 0)
  135. const reactiveUnitIndex = network.key === 'rl' ? inductanceUnitIndex : capacitanceUnitIndex
  136. const resistanceUnit = getOption(RESISTANCE_UNIT_OPTIONS, resistanceUnitIndex)
  137. const reactiveUnit = getOption(reactiveUnitOptions, reactiveUnitIndex)
  138. const frequencyUnit = getOption(FREQUENCY_UNIT_OPTIONS, frequencyUnitIndex)
  139. const resistanceText = String(source.filterResistanceValue || '')
  140. const reactiveText = String(source.filterReactiveValue || '')
  141. const frequencyText = String(source.filterFrequencyValue || '')
  142. const resistanceNumber = parsePositiveNumber(resistanceText)
  143. const reactiveNumber = parsePositiveNumber(reactiveText)
  144. const frequencyNumber = parsePositiveNumber(frequencyText)
  145. const invalidInput = [resistanceNumber, reactiveNumber, frequencyNumber].some((value) => Number.isNaN(value))
  146. const values = {
  147. frequency: Number.isFinite(frequencyNumber) ? frequencyNumber * frequencyUnit.factor : null,
  148. reactive: Number.isFinite(reactiveNumber) ? reactiveNumber * reactiveUnit.factor : null,
  149. resistance: Number.isFinite(resistanceNumber) ? resistanceNumber * resistanceUnit.factor : null
  150. }
  151. const calculated = invalidInput
  152. ? { computedKey: '', errorText: '输入值需大于 0' }
  153. : calculateMissingValue(network.key, values)
  154. const computedValue = Number.isFinite(calculated.value) ? calculated.value : null
  155. const diagram = getDiagramComponents(network.key, response.key)
  156. let resistanceDisplayUnit = resistanceUnit
  157. let resistanceDisplayUnitIndex = resistanceUnitIndex
  158. let reactiveDisplayUnit = reactiveUnit
  159. let reactiveDisplayUnitIndex = reactiveUnitIndex
  160. let frequencyDisplayUnit = frequencyUnit
  161. let frequencyDisplayUnitIndex = frequencyUnitIndex
  162. let resistanceDisplayValue = Number.isFinite(resistanceNumber)
  163. ? formatMagnitudeNumber(resistanceNumber, { fallbackText: '' })
  164. : resistanceText
  165. let reactiveDisplayValue = Number.isFinite(reactiveNumber)
  166. ? formatMagnitudeNumber(reactiveNumber, { fallbackText: '' })
  167. : reactiveText
  168. let frequencyDisplayValue = Number.isFinite(frequencyNumber)
  169. ? formatMagnitudeNumber(frequencyNumber, { fallbackText: '' })
  170. : frequencyText
  171. if (computedValue !== null && calculated.computedKey === 'resistance') {
  172. const selected = selectBestUnit(RESISTANCE_UNIT_OPTIONS, computedValue, resistanceUnitIndex)
  173. resistanceDisplayUnit = selected.unit
  174. resistanceDisplayUnitIndex = selected.index
  175. resistanceDisplayValue = formatScaledValue(computedValue, resistanceDisplayUnit, { fallbackText: '' })
  176. } else if (computedValue !== null && calculated.computedKey === 'reactive') {
  177. const selected = selectBestUnit(reactiveUnitOptions, computedValue, reactiveUnitIndex)
  178. reactiveDisplayUnit = selected.unit
  179. reactiveDisplayUnitIndex = selected.index
  180. reactiveDisplayValue = formatScaledValue(computedValue, reactiveDisplayUnit, { fallbackText: '' })
  181. } else if (computedValue !== null && calculated.computedKey === 'frequency') {
  182. const selected = selectBestUnit(FREQUENCY_UNIT_OPTIONS, computedValue, frequencyUnitIndex)
  183. frequencyDisplayUnit = selected.unit
  184. frequencyDisplayUnitIndex = selected.index
  185. frequencyDisplayValue = formatScaledValue(computedValue, frequencyDisplayUnit, { fallbackText: '' })
  186. }
  187. return {
  188. filterCapacitanceUnitIndex: network.key === 'rc' ? reactiveDisplayUnitIndex : capacitanceUnitIndex,
  189. filterComputedKey: calculated.computedKey || '',
  190. filterErrorText: calculated.errorText || '',
  191. filterFrequencyDisplayValue: frequencyDisplayValue,
  192. filterFrequencyUnitIndex: frequencyDisplayUnitIndex,
  193. filterFrequencyUnitOptions: FREQUENCY_UNIT_OPTIONS,
  194. filterFrequencyUnitText: frequencyDisplayUnit.label,
  195. filterFrequencyValue: frequencyText,
  196. filterFormulaText: network.key === 'rl' ? 'fc = R / (2πL)' : 'fc = 1 / (2πRC)',
  197. filterInductanceUnitIndex: network.key === 'rl' ? reactiveDisplayUnitIndex : inductanceUnitIndex,
  198. filterNetworkIndex: networkIndex,
  199. filterNetworkKey: network.key,
  200. filterNetworkOptions: NETWORK_OPTIONS,
  201. filterNetworkText: network.label,
  202. filterReactiveDisplayValue: reactiveDisplayValue,
  203. filterReactiveName: network.key === 'rl' ? '电感' : '电容',
  204. filterReactiveSymbol: network.key === 'rl' ? 'L' : 'C',
  205. filterReactiveUnitIndex: reactiveDisplayUnitIndex,
  206. filterReactiveUnitOptions: reactiveUnitOptions,
  207. filterReactiveUnitText: reactiveDisplayUnit.label,
  208. filterReactiveValue: reactiveText,
  209. filterResistanceDisplayValue: resistanceDisplayValue,
  210. filterResistanceUnitIndex: resistanceDisplayUnitIndex,
  211. filterResistanceUnitOptions: RESISTANCE_UNIT_OPTIONS,
  212. filterResistanceUnitText: resistanceDisplayUnit.label,
  213. filterResistanceValue: resistanceText,
  214. filterResponseIndex: responseIndex,
  215. filterResponseKey: response.key,
  216. filterResponseOptions: RESPONSE_OPTIONS,
  217. filterResponseText: response.label,
  218. filterSeriesLabel: getDiagramComponentLabel(diagram.series),
  219. filterSeriesComponentSrc: getDiagramComponentSrc(diagram.series),
  220. filterShuntLabel: getDiagramComponentLabel(diagram.shunt),
  221. filterShuntComponentSrc: getDiagramComponentSrc(diagram.shunt)
  222. }
  223. }
  224. function createInitialState() {
  225. return buildState({
  226. filterCapacitanceUnitIndex: 1,
  227. filterFrequencyUnitIndex: 0,
  228. filterInductanceUnitIndex: 3,
  229. filterNetworkIndex: 0,
  230. filterReactiveValue: '',
  231. filterResistanceUnitIndex: 1,
  232. filterResistanceValue: '',
  233. filterResponseIndex: 0
  234. })
  235. }
  236. function updateState(state, changedData = {}) {
  237. return buildState({
  238. ...state,
  239. ...changedData
  240. })
  241. }
  242. function normalizeValue(state, fieldKey, fieldValue) {
  243. const source = buildState(state)
  244. const config = {
  245. frequency: {
  246. options: FREQUENCY_UNIT_OPTIONS,
  247. unitIndex: source.filterFrequencyUnitIndex,
  248. unitIndexKey: 'filterFrequencyUnitIndex',
  249. valueKey: 'filterFrequencyValue'
  250. },
  251. reactive: {
  252. options: source.filterReactiveUnitOptions,
  253. unitIndex: source.filterReactiveUnitIndex,
  254. unitIndexKey: source.filterNetworkKey === 'rl'
  255. ? 'filterInductanceUnitIndex'
  256. : 'filterCapacitanceUnitIndex',
  257. valueKey: 'filterReactiveValue'
  258. },
  259. resistance: {
  260. options: RESISTANCE_UNIT_OPTIONS,
  261. unitIndex: source.filterResistanceUnitIndex,
  262. unitIndexKey: 'filterResistanceUnitIndex',
  263. valueKey: 'filterResistanceValue'
  264. }
  265. }[fieldKey]
  266. if (!config) return source
  267. const text = fieldValue === undefined ? source[config.valueKey] : fieldValue
  268. const numberValue = parsePositiveNumber(text)
  269. if (!Number.isFinite(numberValue)) {
  270. return updateState(source, {
  271. [config.valueKey]: text
  272. })
  273. }
  274. const currentUnit = getOption(config.options, config.unitIndex)
  275. const baseValue = numberValue * currentUnit.factor
  276. const selected = selectBestUnit(config.options, baseValue, config.unitIndex)
  277. return updateState(source, {
  278. [config.unitIndexKey]: selected.index,
  279. [config.valueKey]: formatScaledValue(baseValue, selected.unit, { fallbackText: '' })
  280. })
  281. }
  282. module.exports = {
  283. CAPACITANCE_UNIT_OPTIONS,
  284. FREQUENCY_UNIT_OPTIONS,
  285. INDUCTANCE_UNIT_OPTIONS,
  286. NETWORK_OPTIONS,
  287. RESISTANCE_UNIT_OPTIONS,
  288. RESPONSE_OPTIONS,
  289. createInitialState,
  290. normalizeValue,
  291. updateState
  292. }