You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The form component is exported as `JSONSchemaForm.default` and can be aliases to `Form` with
1207
+
The form component is exported as `JSONSchemaForm.default` and can be aliases to `Form`for easy use with
1196
1208
1197
1209
```{.js #jsonschema-app}
1198
1210
// this JavaScript snippet is appended to <<jsonschema-app>>
@@ -1249,12 +1261,12 @@ The `handleSubmit` function recieves the form input values and use the web worke
1249
1261
// this JavaScript snippet is appended to <<jsonschema-app>>
1250
1262
const [root, setRoot] =React.useState(undefined);
1251
1263
1252
-
functionhandleSubmit({formData}, event) {
1264
+
functionhandleSubmit(submission, event) {
1253
1265
event.preventDefault();
1254
1266
constworker=newWorker('worker.js');
1255
1267
worker.postMessage({
1256
1268
type:'CALCULATE',
1257
-
payload: formData
1269
+
payload:submission.formData
1258
1270
});
1259
1271
worker.onmessage=function(message) {
1260
1272
if (message.data.type==='RESULT') {
@@ -1311,4 +1323,291 @@ If you enter a negative number in the `epsilon` field the form will become inval
1311
1323
1312
1324
### Visualization
1313
1325
1314
-
The plots in web apllicatoin can be made using [vega-lite](https://vega.github.io/vega-lite/). Vega-lite is a JS library which accepts a JSON document describing the plot and generates interactive graphics.
1326
+
The plots in web application can be made using [vega-lite](https://vega.github.io/vega-lite/). Vega-lite is a JS library which accepts a JSON document describing the plot.
1327
+
1328
+
To make an interesting plot we need more than one result. We are going to do a parameter sweep and measure how long each calculation takes.
1329
+
1330
+
Lets make a new JSON schema for the form in which we can set a max, min and step for epsilon.
1331
+
1332
+
```{.js #plot-app}
1333
+
// this JavaScript snippet is later referred to as <<jsonschema-app>>
1334
+
constschema= {
1335
+
"type":"object",
1336
+
"properties": {
1337
+
"epsilon": {
1338
+
"title":"Epsilon",
1339
+
"type":"object",
1340
+
"properties": {
1341
+
"min": {
1342
+
"type":"number",
1343
+
"minimum":0,
1344
+
"default":0.0001
1345
+
},
1346
+
"max": {
1347
+
"type":"number",
1348
+
"minimum":0,
1349
+
"default":0.001
1350
+
},
1351
+
"step": {
1352
+
"type":"number",
1353
+
"minimum":0,
1354
+
"default":0.0001
1355
+
}
1356
+
},
1357
+
"required": ["min", "max", "step"],
1358
+
"additionalProperties":false
1359
+
},
1360
+
"guess": {
1361
+
"title":"Initial guess",
1362
+
"type":"number",
1363
+
"minimum":-100,
1364
+
"maximum":100,
1365
+
"default":-20
1366
+
}
1367
+
},
1368
+
"required": ["epsilon", "guess"],
1369
+
"additionalProperties":false
1370
+
}
1371
+
```
1372
+
1373
+
We need to rewrite the worker to perform a parameter sweep.
1374
+
The worker will recieve a payload like
1375
+
1376
+
```json
1377
+
{
1378
+
"epsilon": {
1379
+
"min": 0.0001,
1380
+
"max": 0.001,
1381
+
"step": 0.0001
1382
+
},
1383
+
"guess": -20
1384
+
}
1385
+
```
1386
+
1387
+
The worker will send back an array containing objects with the root result, the input parameters and the duration in milliseconds.
1388
+
1389
+
```json
1390
+
[{
1391
+
"epsilon": 0.0001,
1392
+
"guess": -20,
1393
+
"root": -1,
1394
+
"duration": 0.61
1395
+
}]
1396
+
```
1397
+
1398
+
To perform the sweep we will first unpack the payload.
1399
+
1400
+
```{.js #calculate-sweep}
1401
+
// this JavaScript snippet is later referred to as <<calculate-sweep>>
// this JavaScript snippet appended to <<calculate-sweep>>
1410
+
constroots= [];
1411
+
```
1412
+
1413
+
Lets use a [classic for loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for) to iterate over requested the epsilons.
1414
+
1415
+
```{.js #calculate-sweep}
1416
+
// this JavaScript snippet appended to <<calculate-sweep>>
To measure the duration of a calculation we use the [performance.now()](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) method which returns a timestamp in milliseconds.
1421
+
1422
+
```{.js #calculate-sweep}
1423
+
// this JavaScript snippet appended to <<calculate-sweep>>
1424
+
constt0=performance.now();
1425
+
constfinder=newmodule.NewtonRaphson(epsilon);
1426
+
constroot=finder.find(guess);
1427
+
constduration=performance.now() - t0;
1428
+
```
1429
+
1430
+
We append the root result object using [shorthand property names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) to the result array.
1431
+
1432
+
```{.js #calculate-sweep}
1433
+
// this JavaScript snippet appended to <<calculate-sweep>>
1434
+
roots.push({
1435
+
epsilon,
1436
+
guess,
1437
+
root,
1438
+
duration
1439
+
});
1440
+
```
1441
+
1442
+
To complete the sweep calculation we need to close the for loop and post the result.
1443
+
1444
+
```{.js #calculate-sweep}
1445
+
// this JavaScript snippet appended to <<calculate-sweep>>
1446
+
}
1447
+
postMessage({
1448
+
type:'RESULT',
1449
+
payload: {
1450
+
roots
1451
+
}
1452
+
});
1453
+
```
1454
+
1455
+
The sweep calculation snippet (`<<calculate-sweep>>`) must be run in a new web worker called `worker-sweep.js`.
1456
+
Like before we need to wait for the WebAssembly module to be initialized before we can start the calculation.
1457
+
1458
+
```{.js file=src/js/worker-sweep.js}
1459
+
// this JavaScript snippet stored as src/js/worker-sweep.js
1460
+
importScripts('newtonraphsonwasm.js');
1461
+
1462
+
onmessage=function(message) {
1463
+
if (message.data.type==='CALCULATE') {
1464
+
createModule().then((module) => {
1465
+
<<calculate-sweep>>
1466
+
});
1467
+
}
1468
+
};
1469
+
```
1470
+
1471
+
To handle the submit we will start a worker, send the form data to the worker, recieve the workers result and store it in the `roots` variable.
1472
+
1473
+
```{.js #plot-app}
1474
+
// this JavaScript snippet is appended to <<plot-app>>
1475
+
const [roots, setRoots] =React.useState([]);
1476
+
1477
+
functionhandleSubmit(submission, event) {
1478
+
event.preventDefault();
1479
+
constworker=newWorker('worker-sweep.js');
1480
+
worker.postMessage({
1481
+
type:'CALCULATE',
1482
+
payload:submission.formData
1483
+
});
1484
+
worker.onmessage=function(message) {
1485
+
if (message.data.type==='RESULT') {
1486
+
constresult=message.data.payload.roots;
1487
+
setRoots(result);
1488
+
worker.terminate();
1489
+
}
1490
+
};
1491
+
}
1492
+
```
1493
+
1494
+
Now that we got data, we are ready to plot. We use the
1495
+
[Vega-Lite specification](https://vega.github.io/vega-lite/docs/spec.html) to declare the plot.
1496
+
The specification for a scatter plot of the `epsilon` against the `duration` looks like.
1497
+
1498
+
```{.js #vega-lite-spec}
1499
+
// this JavaScript snippet is later referred to as <<vega-lite-spec>>
To render the spec we use the [vegaEmbed](https://github.com/vega/vega-embed) module. The Vega-Lite specification is a simplification of the [Vega specification](https://vega.github.io/vega/docs/specification/) so wil first import `vega` then `vega-lite` and lastly `vega-embed`.
The `vegaEmbed()` function needs a DOM element to render the plot in.
1522
+
In React we must use the [useRef](https://reactjs.org/docs/hooks-reference.html#useref) hook to get a reference to a DOM element. As the DOM element needs time to initialize we need to use the [useEffect](https://reactjs.org/docs/hooks-effect.html) hook to only embed the plot when the DOM element is ready. The `Plot` React component can be written as
1523
+
1524
+
```{.jsx #plot-component}
1525
+
// this JavaScript snippet is later referred to as <<plot-component>>
1526
+
functionPlot({roots}) {
1527
+
constcontainer=React.useRef(null);
1528
+
1529
+
functiondidUpdate() {
1530
+
if (container.current===null) {
1531
+
return;
1532
+
}
1533
+
<<vega-lite-spec>>
1534
+
vegaEmbed(container.current, spec);
1535
+
}
1536
+
constdependencies= [container, roots];
1537
+
React.useEffect(didUpdate, dependencies);
1538
+
1539
+
return<div ref={container}/>;
1540
+
}
1541
+
```
1542
+
1543
+
The App component can be defined and rendered with.
1544
+
1545
+
```{.jsx file=src/js/plot-app.js}
1546
+
// this JavaScript snippet stored as src/js/plot-app.js
1547
+
<<heading-component>>
1548
+
1549
+
<<plot-component>>
1550
+
1551
+
functionApp() {
1552
+
constForm=JSONSchemaForm.default;
1553
+
constuiSchema= {
1554
+
"guess": {
1555
+
"ui:widget":"range"
1556
+
}
1557
+
}
1558
+
const [formData, setFormData] =React.useState({
1559
+
1560
+
});
1561
+
1562
+
functionhandleChange(event) {
1563
+
setFormData(event.formData);
1564
+
}
1565
+
1566
+
<<plot-app>>
1567
+
1568
+
return (
1569
+
<div>
1570
+
<Heading/>
1571
+
<<jsonschema-form>>
1572
+
<Plot roots={roots}/>
1573
+
</div>
1574
+
);
1575
+
}
1576
+
1577
+
ReactDOM.render(
1578
+
<App/>,
1579
+
document.getElementById('container')
1580
+
);
1581
+
```
1582
+
1583
+
The html page should look like
1584
+
1585
+
```{.html file=src/js/example-plot.html}
1586
+
<!doctype html>
1587
+
<!-- this HTML page is stored as src/js/plot-form.html -->
0 commit comments