Files
Learn_System/frontend/js/phys.js
T
Maxim Dolgolyov 09cfaa3bd2 fix(phys8): закрытие критических проблем ревью — миграции, ✓→✓, ConvectionSim
- Удалены legacy миграции 009/010/015 (создавали несуществующие physics-8-thermal/electro/optics)
- 037_physics_8_hub.sql сделана self-sufficient: INSERT OR IGNORE родителя + UPDATE
- 7 шт. literal ✓ заменены на ✓ в physics_8_lab.html (правило проекта)
- В phys.js добавлен createConvectionSim — главный визуал §4 (тороидальный поток частиц)
  закрытие плана Phase 0 Физики 8
2026-05-30 09:44:51 +03:00

479 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// phys.js — модуль физических хелперов для учебника Физика 10
// Экспорт в window.PHYS = { ... }
(function(){
'use strict';
// === Стрелка вектора (2D) ===
function drawArrow(x1, y1, x2, y2, color, width, headSize) {
width = width || 2;
headSize = headSize || 10;
const dx = x2 - x1, dy = y2 - y1;
const len = Math.sqrt(dx*dx + dy*dy);
if (len < 1e-6) return '';
const ux = dx / len, uy = dy / len;
const px = -uy, py = ux;
const h = headSize, w = headSize * 0.6;
const bx = x2 - ux*h, by = y2 - uy*h;
const lx = bx + px*w, ly = by + py*w;
const rx = bx - px*w, ry = by - py*w;
return `<line x1="${x1.toFixed(1)}" y1="${y1.toFixed(1)}" x2="${bx.toFixed(1)}" y2="${by.toFixed(1)}" stroke="${color}" stroke-width="${width}" stroke-linecap="round"/>`
+ `<polygon points="${x2.toFixed(1)},${y2.toFixed(1)} ${lx.toFixed(1)},${ly.toFixed(1)} ${rx.toFixed(1)},${ry.toFixed(1)}" fill="${color}"/>`;
}
// === Линии электрического поля от точечного заряда ===
function fieldLinesPointCharge(cx, cy, sign, scale, numLines) {
numLines = numLines || 16;
scale = scale || 80;
let s = '';
const color = sign > 0 ? '#dc2626' : '#2563eb';
for (let i = 0; i < numLines; i++) {
const a = 2 * Math.PI * i / numLines;
const r1 = 18, r2 = scale;
const x1 = cx + r1*Math.cos(a), y1 = cy + r1*Math.sin(a);
const x2 = cx + r2*Math.cos(a), y2 = cy + r2*Math.sin(a);
if (sign > 0) s += drawArrow(x1, y1, x2, y2, color, 1.4, 7);
else s += drawArrow(x2, y2, x1, y1, color, 1.4, 7);
}
return s;
}
// === Обозначение заряда (кружок с +/-) ===
function chargeMark(cx, cy, sign, r, label) {
r = r || 14;
const color = sign > 0 ? '#dc2626' : '#2563eb';
const fill = sign > 0 ? '#fecaca' : '#bfdbfe';
let s = '';
s += `<circle cx="${cx}" cy="${cy}" r="${r}" fill="${fill}" stroke="${color}" stroke-width="2"/>`;
if (sign > 0) {
s += `<line x1="${cx-r*0.5}" y1="${cy}" x2="${cx+r*0.5}" y2="${cy}" stroke="${color}" stroke-width="2.5"/>`;
s += `<line x1="${cx}" y1="${cy-r*0.5}" x2="${cx}" y2="${cy+r*0.5}" stroke="${color}" stroke-width="2.5"/>`;
} else {
s += `<line x1="${cx-r*0.5}" y1="${cy}" x2="${cx+r*0.5}" y2="${cy}" stroke="${color}" stroke-width="2.5"/>`;
}
if (label) {
s += `<text x="${cx+r+4}" y="${cy+4}" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="${color}">${label}</text>`;
}
return s;
}
// === Магнитное поле сквозь экран (сетка крестиков или точек) ===
function magneticFieldGrid(x0, y0, w, h, cols, rows, direction) {
// direction: 'in' = крест (× — вошло в плоскость), 'out' = точка (• — вышло)
let s = '';
const dx = w / (cols - 1), dy = h / (rows - 1);
const color = '#7c3aed';
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const cx = x0 + i * dx, cy = y0 + j * dy;
s += `<circle cx="${cx}" cy="${cy}" r="6" fill="white" stroke="${color}" stroke-width="1.3"/>`;
if (direction === 'in') {
s += `<line x1="${cx-4}" y1="${cy-4}" x2="${cx+4}" y2="${cy+4}" stroke="${color}" stroke-width="1.5"/>`;
s += `<line x1="${cx-4}" y1="${cy+4}" x2="${cx+4}" y2="${cy-4}" stroke="${color}" stroke-width="1.5"/>`;
} else {
s += `<circle cx="${cx}" cy="${cy}" r="2.2" fill="${color}"/>`;
}
}
}
return s;
}
// === Молекула газа (частица) ===
function molecule(x, y, r, color) {
r = r || 4;
color = color || '#2563eb';
return `<circle cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="${r}" fill="${color}" stroke="#0f172a" stroke-width="0.6"/>`;
}
// === Симуляция газа (упругое столкновение со стенками) ===
function createGasSim(opts) {
opts = opts || {};
const N = opts.N || 30;
const W = opts.W || 320;
const H = opts.H || 220;
const baseSpeed = opts.speed || 60; // px/s
const r = opts.r || 4;
const particles = [];
for (let i = 0; i < N; i++) {
particles.push({
x: r + Math.random() * (W - 2*r),
y: r + Math.random() * (H - 2*r),
vx: (Math.random() - 0.5) * 2 * baseSpeed,
vy: (Math.random() - 0.5) * 2 * baseSpeed
});
}
return {
W: W, H: H, r: r, particles: particles,
step(dt) {
for (const p of particles) {
p.x += p.vx * dt; p.y += p.vy * dt;
if (p.x < r) { p.x = r; p.vx = -p.vx; }
if (p.x > W - r) { p.x = W - r; p.vx = -p.vx; }
if (p.y < r) { p.y = r; p.vy = -p.vy; }
if (p.y > H - r) { p.y = H - r; p.vy = -p.vy; }
}
},
render(color) {
color = color || '#2563eb';
return particles.map(p => molecule(p.x, p.y, r, color)).join('');
},
setSpeed(scale) {
for (const p of particles) { p.vx *= scale; p.vy *= scale; }
}
};
}
// === Электрические схемы: компоненты ===
// orientation: 'h' (горизонтально, по умолчанию) или 'v' (вертикально)
function batteryEMF(x, y, EMF, orientation) {
orientation = orientation || 'h';
let s = '';
if (orientation === 'h') {
s += `<line x1="${x-3}" y1="${y-18}" x2="${x-3}" y2="${y+18}" stroke="#0f172a" stroke-width="2.5"/>`;
s += `<line x1="${x+3}" y1="${y-9}" x2="${x+3}" y2="${y+9}" stroke="#0f172a" stroke-width="2.5"/>`;
s += `<text x="${x-3}" y="${y-24}" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">+</text>`;
s += `<text x="${x+3}" y="${y-24}" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0f172a">&#8722;</text>`;
if (EMF !== undefined) s += `<text x="${x}" y="${y+34}" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#0f172a">&#949; = ${EMF}</text>`;
} else {
s += `<line x1="${x-18}" y1="${y-3}" x2="${x+18}" y2="${y-3}" stroke="#0f172a" stroke-width="2.5"/>`;
s += `<line x1="${x-9}" y1="${y+3}" x2="${x+9}" y2="${y+3}" stroke="#0f172a" stroke-width="2.5"/>`;
if (EMF !== undefined) s += `<text x="${x+24}" y="${y+4}" font-family="JetBrains Mono,monospace" font-size="12" fill="#0f172a">&#949; = ${EMF}</text>`;
}
return s;
}
function resistor(x, y, R, orientation) {
orientation = orientation || 'h';
let s = '';
if (orientation === 'h') {
s += `<rect x="${x-20}" y="${y-7}" width="40" height="14" fill="white" stroke="#0f172a" stroke-width="1.6"/>`;
if (R !== undefined) s += `<text x="${x}" y="${y+25}" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#0f172a">R = ${R}</text>`;
} else {
s += `<rect x="${x-7}" y="${y-20}" width="14" height="40" fill="white" stroke="#0f172a" stroke-width="1.6"/>`;
if (R !== undefined) s += `<text x="${x+18}" y="${y+4}" font-family="JetBrains Mono,monospace" font-size="12" fill="#0f172a">R = ${R}</text>`;
}
return s;
}
function capacitorSymbol(x, y, C, orientation) {
orientation = orientation || 'h';
let s = '';
if (orientation === 'h') {
s += `<line x1="${x-3}" y1="${y-14}" x2="${x-3}" y2="${y+14}" stroke="#0f172a" stroke-width="2.5"/>`;
s += `<line x1="${x+3}" y1="${y-14}" x2="${x+3}" y2="${y+14}" stroke="#0f172a" stroke-width="2.5"/>`;
if (C !== undefined) s += `<text x="${x}" y="${y+30}" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#0f172a">C = ${C}</text>`;
}
return s;
}
function ammeterSymbol(x, y, r) {
r = r || 14;
return `<circle cx="${x}" cy="${y}" r="${r}" fill="white" stroke="#0f172a" stroke-width="1.6"/>`
+ `<text x="${x}" y="${y+5}" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#d97706">A</text>`;
}
function voltmeterSymbol(x, y, r) {
r = r || 14;
return `<circle cx="${x}" cy="${y}" r="${r}" fill="white" stroke="#0f172a" stroke-width="1.6"/>`
+ `<text x="${x}" y="${y+5}" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#2563eb">V</text>`;
}
function lightbulbSymbol(x, y, r) {
r = r || 14;
let s = '';
s += `<circle cx="${x}" cy="${y}" r="${r}" fill="#fef3c7" stroke="#0f172a" stroke-width="1.6"/>`;
s += `<line x1="${x-r*0.7}" y1="${y-r*0.7}" x2="${x+r*0.7}" y2="${y+r*0.7}" stroke="#0f172a" stroke-width="1.4"/>`;
s += `<line x1="${x-r*0.7}" y1="${y+r*0.7}" x2="${x+r*0.7}" y2="${y-r*0.7}" stroke="#0f172a" stroke-width="1.4"/>`;
return s;
}
function inductorSymbol(x, y, L, orientation) {
orientation = orientation || 'h';
let s = '';
if (orientation === 'h') {
for (let i = 0; i < 4; i++) {
const cx = x - 15 + i * 10;
s += `<path d="M ${cx-5} ${y} A 5 5 0 0 1 ${cx+5} ${y}" fill="none" stroke="#0f172a" stroke-width="1.6"/>`;
}
if (L !== undefined) s += `<text x="${x}" y="${y+22}" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#0f172a">L = ${L}</text>`;
}
return s;
}
function wire(x1, y1, x2, y2) {
return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#0f172a" stroke-width="1.6"/>`;
}
// === Эталонные константы ===
const CONST = {
k: 9e9, // Кулона
e: 1.6e-19, // элементарный заряд
eps0: 8.85e-12, // электрическая постоянная
kB: 1.38e-23, // Больцмана
NA: 6.022e23, // Авогадро
R: 8.314, // универсальная газовая
c: 3e8, // скорость света
g: 9.8, // ускорение свободного падения
atm: 101325, // 1 атм в Па
T0: 273.15 // ноль Цельсия в К
};
// === Конвертеры единиц ===
function celsiusToKelvin(t) { return t + 273.15; }
function kelvinToCelsius(T) { return T - 273.15; }
function atmToPa(p) { return p * 101325; }
function paToAtm(p) { return p / 101325; }
function litersToM3(V) { return V / 1000; }
function m3ToLiters(V) { return V * 1000; }
// === Расширения для Физики 8 (тепловые явления) ===
// === Цвет по температуре: 240° (синий) → 0° (красный) ===
function tempColor(t, tMin, tMax) {
const u = Math.max(0, Math.min(1, (t - tMin) / (tMax - tMin)));
const hue = 240 * (1 - u);
return `hsl(${hue.toFixed(0)},72%,52%)`;
}
// === Термометр (вертикальная шкала, столбик ртути по температуре) ===
function thermometer(x, y, h, tMin, tMax, tValue) {
// x, y — верх корпуса; h — высота столбика
const reservoirR = 11;
const tubeW = 8;
const u = Math.max(0, Math.min(1, (tValue - tMin) / (tMax - tMin)));
const fillH = h * u;
const color = tempColor(tValue, tMin, tMax);
let s = '';
// Корпус (стекло)
s += `<rect x="${x - tubeW/2}" y="${y}" width="${tubeW}" height="${h}" rx="${tubeW/2}" fill="#fff" stroke="#0f172a" stroke-width="1.4"/>`;
// Столбик ртути
s += `<rect x="${x - tubeW/2 + 1.5}" y="${y + h - fillH}" width="${tubeW - 3}" height="${fillH}" rx="${(tubeW-3)/2}" fill="${color}"/>`;
// Резервуар
s += `<circle cx="${x}" cy="${y + h + reservoirR - 1}" r="${reservoirR}" fill="${color}" stroke="#0f172a" stroke-width="1.4"/>`;
// Деления (5)
for (let i = 0; i <= 5; i++) {
const ty = y + h * i / 5;
const tv = tMax - (tMax - tMin) * i / 5;
s += `<line x1="${x + tubeW/2 + 1}" y1="${ty}" x2="${x + tubeW/2 + 6}" y2="${ty}" stroke="#0f172a" stroke-width="1"/>`;
s += `<text x="${x + tubeW/2 + 9}" y="${ty + 3.5}" font-family="JetBrains Mono,monospace" font-size="10" fill="#0f172a">${tv.toFixed(0)}</text>`;
}
// Подпись текущего значения
s += `<text x="${x - tubeW/2 - 4}" y="${y - 4}" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="${color}">${tValue.toFixed(0)} &#176;C</text>`;
return s;
}
// === Калориметр-стакан с жидкостью ===
function calorimeter(x, y, w, h, fillFrac, liquidColor) {
fillFrac = Math.max(0, Math.min(1, fillFrac || 0.6));
liquidColor = liquidColor || '#60a5fa';
const wall = 4;
const innerH = h - wall;
const liqH = innerH * fillFrac;
let s = '';
// Внешний контур
s += `<path d="M ${x} ${y} L ${x} ${y+h} Q ${x} ${y+h+6} ${x+8} ${y+h+6} L ${x+w-8} ${y+h+6} Q ${x+w} ${y+h+6} ${x+w} ${y+h} L ${x+w} ${y} L ${x+w-wall} ${y} L ${x+w-wall} ${y+h-2} L ${x+wall} ${y+h-2} L ${x+wall} ${y} Z" fill="#e0f2fe" stroke="#0f172a" stroke-width="1.5"/>`;
// Жидкость
s += `<rect x="${x+wall+1}" y="${y + innerH - liqH + wall/2}" width="${w - 2*wall - 2}" height="${liqH}" fill="${liquidColor}" opacity="0.78"/>`;
// Мениск
s += `<line x1="${x+wall+1}" y1="${y + innerH - liqH + wall/2}" x2="${x+w-wall-1}" y2="${y + innerH - liqH + wall/2}" stroke="${liquidColor}" stroke-width="1.6" opacity="0.95"/>`;
return s;
}
// === Симуляция теплопроводности по стержню (одномерное уравнение тепла) ===
// Возвращает объект с .step(dt), .render(x, y, w, h), .reset(), .setTHot(t), .setTCold(t)
function createHeatBar(opts) {
opts = opts || {};
const N = opts.N || 30;
const tHot = opts.tHot != null ? opts.tHot : 200;
const tCold = opts.tCold != null ? opts.tCold : 0;
const alpha = opts.alpha || 0.25; // коэф. диффузии (отн. ед.)
const T = new Array(N);
for (let i = 0; i < N; i++) T[i] = tCold;
return {
N: N, alpha: alpha, T: T,
_tHot: tHot, _tCold: tCold,
setTHot(v) { this._tHot = v; },
setTCold(v) { this._tCold = v; },
reset() {
for (let i = 0; i < this.N; i++) this.T[i] = this._tCold;
},
step(dt) {
// Конечно-разностное уравнение теплопроводности с граничными условиями Дирихле.
const Tnew = new Array(this.N);
Tnew[0] = this._tHot;
Tnew[this.N - 1] = this._tCold;
for (let i = 1; i < this.N - 1; i++) {
Tnew[i] = this.T[i] + this.alpha * dt * (this.T[i-1] - 2*this.T[i] + this.T[i+1]);
}
this.T = Tnew;
},
render(x, y, w, h) {
const segW = w / this.N;
const tMin = Math.min(this._tCold, this._tHot);
const tMax = Math.max(this._tCold, this._tHot);
let s = '';
for (let i = 0; i < this.N; i++) {
const c = tempColor(this.T[i], tMin, tMax);
s += `<rect x="${(x + i*segW).toFixed(1)}" y="${y}" width="${(segW + 0.5).toFixed(1)}" height="${h}" fill="${c}"/>`;
}
// Контур стержня
s += `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="none" stroke="#0f172a" stroke-width="1.5"/>`;
return s;
}
};
}
// === График фазовых переходов T(t) с горизонтальными плато ===
// points: массив сегментов [{tStart, tEnd, Tstart, Tend, label?}]
// W, H, pad — размеры графика; tMaxAll, TminAll, TmaxAll — диапазоны
function phaseGraphTT(W, H, pad, points, tMaxAll, TminAll, TmaxAll) {
const toX = t => pad + (W - 2*pad) * t / tMaxAll;
const toY = T => H - pad - (H - 2*pad) * (T - TminAll) / (TmaxAll - TminAll);
let s = '';
// Оси
s += `<line x1="${pad}" y1="${H-pad}" x2="${W-pad}" y2="${H-pad}" stroke="#0f172a" stroke-width="1.5"/>`;
s += `<line x1="${pad}" y1="${pad}" x2="${pad}" y2="${H-pad}" stroke="#0f172a" stroke-width="1.5"/>`;
s += `<text x="${W-pad+4}" y="${H-pad+4}" font-family="Inter,sans-serif" font-size="11" fill="#0f172a">t</text>`;
s += `<text x="${pad-4}" y="${pad-4}" text-anchor="end" font-family="Inter,sans-serif" font-size="11" fill="#0f172a">T,&#176;C</text>`;
// Сегменты
let d = '';
let first = true;
for (const seg of points) {
if (first) { d += `M ${toX(seg.tStart).toFixed(1)} ${toY(seg.Tstart).toFixed(1)} `; first = false; }
d += `L ${toX(seg.tEnd).toFixed(1)} ${toY(seg.Tend).toFixed(1)} `;
}
s += `<path d="${d}" stroke="#dc2626" stroke-width="2.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>`;
// Подписи сегментов
for (const seg of points) {
if (seg.label) {
const mx = (toX(seg.tStart) + toX(seg.tEnd)) / 2;
const my = (toY(seg.Tstart) + toY(seg.Tend)) / 2 - 8;
s += `<text x="${mx.toFixed(1)}" y="${my.toFixed(1)}" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="600" fill="#475569">${seg.label}</text>`;
}
}
return { svg: s, toX: toX, toY: toY };
}
// === Анимация конвекции — тороидальный поток частиц ===
// Используется в §4 Физики 8 (главный визуал «конвекция в жидкости/газе»).
// opts: { N — число частиц (default 24), w, h — размеры области, speed (отн. ед., default 1),
// tHot, tCold — температуры для окраски (default 100, 20) }.
function createConvectionSim(opts) {
opts = opts || {};
const N = opts.N || 24;
const w = opts.w || 220;
const h = opts.h || 140;
const speed = opts.speed != null ? opts.speed : 1;
const tHot = opts.tHot != null ? opts.tHot : 100;
const tCold = opts.tCold != null ? opts.tCold : 20;
// Каждая частица движется по фазе вдоль контура прямоугольника:
// правая сторона — вверх (нагрев / подъём), верхняя — влево, левая — вниз (охлаждение), нижняя — вправо.
const parts = new Array(N);
for (let i = 0; i < N; i++) parts[i] = { phase: i / N };
return {
N: N, w: w, h: h, speed: speed,
_tHot: tHot, _tCold: tCold,
setSpeed(v) { this.speed = v; },
setHot(v) { this._tHot = v; },
setCold(v) { this._tCold = v; },
step(dt) {
const dPhase = 0.18 * this.speed * dt;
for (let i = 0; i < this.N; i++) {
parts[i].phase = (parts[i].phase + dPhase) % 1;
if (parts[i].phase < 0) parts[i].phase += 1;
}
},
// Возвращает координаты частицы (cx, cy) и температуру по её положению.
// phase ∈ [0,1): 0..0.25 — правая (подъём, t→tHot), 0.25..0.5 — верх (t=tHot),
// 0.5..0.75 — левая (опускание, t→tCold), 0.75..1 — низ (t=tCold).
_xy(phase, x, y) {
const margin = 12;
const W = this.w - 2 * margin;
const H = this.h - 2 * margin;
let lx, ly, t;
if (phase < 0.25) {
const k = phase / 0.25;
lx = W; ly = H * (1 - k);
t = this._tCold + (this._tHot - this._tCold) * k;
} else if (phase < 0.5) {
const k = (phase - 0.25) / 0.25;
lx = W * (1 - k); ly = 0;
t = this._tHot;
} else if (phase < 0.75) {
const k = (phase - 0.5) / 0.25;
lx = 0; ly = H * k;
t = this._tHot - (this._tHot - this._tCold) * k;
} else {
const k = (phase - 0.75) / 0.25;
lx = W * k; ly = H;
t = this._tCold;
}
return { cx: x + margin + lx, cy: y + margin + ly, t: t };
},
render(x, y) {
const tMin = Math.min(this._tCold, this._tHot);
const tMax = Math.max(this._tCold, this._tHot);
let s = '';
// Контур сосуда
s += `<rect x="${x}" y="${y}" width="${this.w}" height="${this.h}" fill="#dbeafe" stroke="#0f172a" stroke-width="1.5" rx="6"/>`;
// Нагреватель снизу (красная полоса)
s += `<rect x="${x + 8}" y="${y + this.h - 6}" width="${this.w - 16}" height="6" fill="#dc2626" opacity="0.85" rx="3"/>`;
// Частицы
for (let i = 0; i < this.N; i++) {
const p = this._xy(parts[i].phase, x, y);
const c = tempColor(p.t, tMin, tMax);
s += `<circle cx="${p.cx.toFixed(1)}" cy="${p.cy.toFixed(1)}" r="4" fill="${c}" stroke="#0f172a" stroke-width="0.6" opacity="0.9"/>`;
}
return s;
}
};
}
// === Электронные хелперы для электрических задач ===
// Параллельное и последовательное сопротивление
function Rseries() {
let R = 0;
for (let i = 0; i < arguments.length; i++) R += arguments[i];
return R;
}
function Rparallel() {
let inv = 0;
for (let i = 0; i < arguments.length; i++) {
if (arguments[i] > 0) inv += 1 / arguments[i];
}
return inv > 0 ? 1 / inv : Infinity;
}
// === Экспорт ===
window.PHYS = {
tempColor: tempColor,
thermometer: thermometer,
calorimeter: calorimeter,
createHeatBar: createHeatBar,
createConvectionSim: createConvectionSim,
phaseGraphTT: phaseGraphTT,
Rseries: Rseries,
Rparallel: Rparallel,
drawArrow: drawArrow,
fieldLinesPointCharge: fieldLinesPointCharge,
chargeMark: chargeMark,
magneticFieldGrid: magneticFieldGrid,
molecule: molecule,
createGasSim: createGasSim,
batteryEMF: batteryEMF,
resistor: resistor,
capacitorSymbol: capacitorSymbol,
ammeterSymbol: ammeterSymbol,
voltmeterSymbol: voltmeterSymbol,
lightbulbSymbol: lightbulbSymbol,
inductorSymbol: inductorSymbol,
wire: wire,
CONST: CONST,
celsiusToKelvin: celsiusToKelvin,
kelvinToCelsius: kelvinToCelsius,
atmToPa: atmToPa,
paToAtm: paToAtm,
litersToM3: litersToM3,
m3ToLiters: m3ToLiters
};
})();