Skip to content

Commit 20da0b7

Browse files
authored
feat: init
1 parent fab966e commit 20da0b7

1 file changed

Lines changed: 293 additions & 0 deletions

File tree

index.html

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
<!DOCTYPE html>
2+
<html lang="ru">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Graphics</title>
6+
<style>
7+
* { margin:0; padding:0; box-sizing:border-box; }
8+
body { font-family:'Consolas',monospace; display:flex; height:100vh; background:#1e1e1e; color:#d4d4d4; }
9+
canvas { width:100%; height:100%; cursor:crosshair; }
10+
#editor { width:35%; display:flex; flex-direction:column; border-right:2px solid #333; }
11+
#graph { width:65%; position:relative; background:#fff; }
12+
#editor-header { background:#252526; padding:10px 15px; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #333; }
13+
#editor-header h2 { font-size:14px; color:#4ec9b0; }
14+
#run-btn { background:#0e639c; color:white; border:none; padding:6px 20px; cursor:pointer; font-family:inherit; font-size:13px; border-radius:3px; }
15+
#run-btn:hover { background:#1177bb; }
16+
#code { flex:1; background:#1e1e1e; color:#d4d4d4; border:none; padding:15px; font-family:'Consolas',monospace; font-size:14px; resize:none; outline:none; line-height:1.6; }
17+
#controls { position:absolute; top:10px; right:10px; background:rgba(255,255,255,0.95); padding:8px; border-radius:5px; }
18+
#controls button { background:#f0f0f0; border:1px solid #ccc; padding:5px 10px; margin:2px; cursor:pointer; border-radius:3px; font-family:inherit; }
19+
#info { position:absolute; bottom:10px; left:10px; background:rgba(255,255,255,0.95); padding:8px 12px; border-radius:5px; font-size:12px; color:#333; }
20+
#msg { position:absolute; bottom:10px; right:10px; padding:8px 12px; border-radius:5px; font-size:12px; display:none; }
21+
#msg.err { background:rgba(255,0,0,0.9); color:white; }
22+
#msg.ok { background:rgba(0,200,0,0.9); color:black; }
23+
</style>
24+
</head>
25+
<body>
26+
<div id="editor">
27+
<div id="editor-header">
28+
<h2>📝 Editor</h2>
29+
<button id="run-btn" onclick="runCode()">▶ RUN</button>
30+
</div>
31+
<textarea id="code" spellcheck="false">
32+
primes = primes(20000);
33+
34+
draw.append(x => pix(x, primes));
35+
36+
/*for (let i = 0; i < primes.length / 100; i++) {
37+
const p = primes[i];
38+
const f = (x) => x > p ? sin(x * (PI / p)) * p : 0;
39+
draw.append(x => f(x), { color: randomColor(), width: 1.5 });
40+
}*/
41+
42+
/*for (let i = 0; i < 10.0; i += 0.05) {
43+
let ecolor = randomColor();
44+
draw.append(x => sqrt(pow(x, 3) - 2 * x + 2.0 / i), { color: ecolor });
45+
draw.append(x => -sqrt(pow(x, 3) - 2 * x + 2.0 / i), { color: ecolor });
46+
}*/
47+
</textarea>
48+
</div>
49+
<div id="graph">
50+
<canvas id="c"></canvas>
51+
<div id="controls">
52+
<button onclick="resetView()"></button>
53+
</div>
54+
<div id="info">Graphics: <span id="cnt">0</span> | Scale: <span id="sc">1.0</span></div>
55+
<div id="msg"></div>
56+
</div>
57+
58+
<script>
59+
const canvas = document.getElementById('c');
60+
const ctx = canvas.getContext('2d');
61+
let scale = 50, cx = 0, cy = 0;
62+
let graphs = [], dragging = false, lx = 0, ly = 0;
63+
64+
const sin=Math.sin, cos=Math.cos, tan=Math.tan, log=Math.log, log2=Math.log2, log10=Math.log10;
65+
const sqrt=Math.sqrt, abs=Math.abs, exp=Math.exp, pow=Math.pow, PI=Math.PI, E=Math.E;
66+
const asin=Math.asin, acos=Math.acos, atan=Math.atan, atan2=Math.atan2;
67+
const sinh=Math.sinh, cosh=Math.cosh, tanh=Math.tanh;
68+
const floor=Math.floor, ceil=Math.ceil, round=Math.round, sign=Math.sign;
69+
const min=Math.min, max=Math.max, random=Math.random;
70+
function primes(n) {
71+
const result = [];
72+
for (let i = 2; i <= n; i++) {
73+
let isPrime = true;
74+
for (let j = 2; j * j <= i; j++) {
75+
if (i % j === 0) { isPrime = false; break; }
76+
}
77+
if (isPrime) result.push(i);
78+
}
79+
return result;
80+
}
81+
82+
function pix(x, primes) {
83+
let left = 0;
84+
let right = primes.length - 1;
85+
let result = 0;
86+
while (left <= right) {
87+
const mid = Math.floor((left + right) / 2);
88+
if (primes[mid] <= x) {
89+
result = mid + 1;
90+
left = mid + 1;
91+
} else {
92+
right = mid - 1;
93+
}
94+
}
95+
return result;
96+
}
97+
98+
99+
function randomColor() {
100+
const h = Math.floor(Math.random()*360);
101+
return `hsl(${h},70%,55%)`;
102+
}
103+
104+
const draw = {
105+
append: (fn, opts={}) => {
106+
graphs.push({
107+
fn,
108+
color: opts.color || randomColor(),
109+
width: opts.width || 2,
110+
opacity: opts.opacity ?? 0.8
111+
});
112+
},
113+
clear: () => { graphs = []; }
114+
};
115+
116+
function resize() {
117+
const p = document.getElementById('graph');
118+
canvas.width = p.clientWidth;
119+
canvas.height = p.clientHeight;
120+
render();
121+
}
122+
window.addEventListener('resize', resize);
123+
124+
canvas.addEventListener('mousedown', e => { dragging=true; lx=e.clientX; ly=e.clientY; });
125+
canvas.addEventListener('mousemove', e => {
126+
if (!dragging) return;
127+
cx -= (e.clientX-lx)/scale;
128+
cy += (e.clientY-ly)/scale;
129+
lx=e.clientX; ly=e.clientY;
130+
render();
131+
});
132+
canvas.addEventListener('mouseup', () => dragging=false);
133+
canvas.addEventListener('mouseleave', () => dragging=false);
134+
canvas.addEventListener('wheel', e => { e.preventDefault(); zoom(e.deltaY>0?0.9:1.1); });
135+
136+
function zoom(f) { scale *= f; render(); }
137+
function resetView() { scale=50; cx=0; cy=0; render(); }
138+
139+
function toScreen(x, y) {
140+
return [(x-cx)*scale + canvas.width/2, canvas.height/2 - (y-cy)*scale];
141+
}
142+
143+
function toMath(px, py) {
144+
return [(px - canvas.width/2)/scale + cx, -(py - canvas.height/2)/scale + cy];
145+
}
146+
147+
function render() {
148+
const W = canvas.width, H = canvas.height;
149+
ctx.clearRect(0,0,W,H);
150+
ctx.fillStyle = '#1a1a1a';
151+
ctx.fillRect(0,0,W,H);
152+
const [xMin, yMax] = toMath(0, 0);
153+
const [xMax, yMin] = toMath(W, H);
154+
let gridStep = 1;
155+
const pixelsPerUnit = scale;
156+
if (pixelsPerUnit < 20) gridStep = 5;
157+
else if (pixelsPerUnit < 40) gridStep = 2;
158+
else if (pixelsPerUnit > 200) gridStep = 0.5;
159+
else if (pixelsPerUnit > 400) gridStep = 0.25;
160+
ctx.strokeStyle = '#2d2d2d';
161+
ctx.lineWidth = 1;
162+
ctx.beginPath();
163+
for (let x = Math.floor(xMin/gridStep)*gridStep; x <= xMax; x += gridStep) {
164+
const px = (x-cx)*scale + W/2;
165+
ctx.moveTo(px, 0);
166+
ctx.lineTo(px, H);
167+
}
168+
for (let y = Math.floor(yMin/gridStep)*gridStep; y <= yMax; y += gridStep) {
169+
const py = H/2 - (y-cy)*scale;
170+
ctx.moveTo(0, py);
171+
ctx.lineTo(W, py);
172+
}
173+
ctx.stroke();
174+
const [axisX_px] = toScreen(0, 0);
175+
const [, axisY_px] = toScreen(0, 0);
176+
ctx.strokeStyle = '#545454';
177+
ctx.lineWidth = 2;
178+
ctx.beginPath();
179+
if (axisY_px >= 0 && axisY_px <= H) {
180+
ctx.moveTo(0, axisY_px);
181+
ctx.lineTo(W, axisY_px);
182+
ctx.moveTo(W-12, axisY_px-6);
183+
ctx.lineTo(W, axisY_px);
184+
ctx.lineTo(W-12, axisY_px+6);
185+
}
186+
if (axisX_px >= 0 && axisX_px <= W) {
187+
ctx.moveTo(axisX_px, H);
188+
ctx.lineTo(axisX_px, 0);
189+
ctx.moveTo(axisX_px-6, 12);
190+
ctx.lineTo(axisX_px, 0);
191+
ctx.lineTo(axisX_px+6, 12);
192+
}
193+
ctx.stroke();
194+
ctx.fillStyle = '#555';
195+
ctx.font = '11px Consolas';
196+
ctx.textAlign = 'center';
197+
for (let x = Math.floor(xMin/gridStep)*gridStep; x <= xMax; x += gridStep) {
198+
if (Math.abs(x) < 0.001) continue;
199+
const px = (x-cx)*scale + W/2;
200+
const labelY = Math.max(12, Math.min(H-5, axisY_px + 16));
201+
ctx.fillText(parseFloat(x.toFixed(4)).toString(), px, labelY);
202+
}
203+
204+
ctx.textAlign = 'right';
205+
for (let y = Math.floor(yMin/gridStep)*gridStep; y <= yMax; y += gridStep) {
206+
if (Math.abs(y) < 0.001) continue;
207+
const py = H/2 - (y-cy)*scale;
208+
const labelX = Math.max(20, Math.min(W-5, axisX_px - 8));
209+
ctx.fillText(parseFloat(y.toFixed(4)).toString(), labelX, py + 4);
210+
}
211+
212+
if (axisX_px >= 0 && axisX_px <= W && axisY_px >= 0 && axisY_px <= H) {
213+
ctx.textAlign = 'right';
214+
ctx.fillText('0', axisX_px - 8, axisY_px + 16);
215+
}
216+
217+
graphs.forEach(g => {
218+
ctx.strokeStyle = g.color;
219+
ctx.lineWidth = g.width;
220+
ctx.globalAlpha = g.opacity;
221+
ctx.beginPath();
222+
223+
let first = true;
224+
let prevPy = null;
225+
const step = 1 / scale;
226+
for (let px = 0; px <= W; px += 1) {
227+
const [x] = toMath(px, 0);
228+
try {
229+
const y = g.fn(x);
230+
if (typeof y !== 'number' || !isFinite(y) || Math.abs(y) > 1e8) {
231+
first = true;
232+
prevPy = null;
233+
continue;
234+
}
235+
const [, py] = toScreen(x, y);
236+
if (prevPy !== null && Math.abs(py - prevPy) > H * 0.8) {
237+
first = true;
238+
}
239+
240+
if (first) {
241+
ctx.moveTo(px, py);
242+
first = false;
243+
} else {
244+
ctx.lineTo(px, py);
245+
}
246+
prevPy = py;
247+
} catch(e) {
248+
first = true;
249+
prevPy = null;
250+
}
251+
}
252+
ctx.stroke();
253+
});
254+
255+
ctx.globalAlpha = 1;
256+
document.getElementById('sc').textContent = (scale/50).toFixed(2);
257+
}
258+
259+
function showMsg(text, type) {
260+
const m = document.getElementById('msg');
261+
m.textContent = text;
262+
m.className = type;
263+
m.style.display = 'block';
264+
setTimeout(() => m.style.display = 'none', 3000);
265+
}
266+
267+
function runCode() {
268+
const code = document.getElementById('code').value;
269+
graphs = [];
270+
try {
271+
const fn = new Function('draw','randomColor','sin','cos','tan','log','log2','log10',
272+
'sqrt','abs','exp','pow','PI','E','asin','acos','atan','atan2',
273+
'sinh','cosh','tanh','floor','ceil','round','sign','min','max','random','primes', 'pix',
274+
code);
275+
fn(draw, randomColor, sin,cos,tan,log,log2,log10, sqrt,abs,exp,pow,PI,E,
276+
asin,acos,atan,atan2, sinh,cosh,tanh, floor,ceil,round,sign,min,max,random,primes,pix);
277+
document.getElementById('cnt').textContent = graphs.length;
278+
render();
279+
showMsg(`✓ Build: ${graphs.length}`, 'ok');
280+
} catch(e) {
281+
showMsg(`✗ ${e.message}`, 'err');
282+
}
283+
}
284+
285+
document.getElementById('code').addEventListener('keydown', e => {
286+
if (e.ctrlKey && e.key === 'Enter') runCode();
287+
});
288+
289+
resize();
290+
setTimeout(runCode, 100);
291+
</script>
292+
</body>
293+
</html>

0 commit comments

Comments
 (0)