Skip to content

Commit d59fddd

Browse files
committed
refactor: add editor support with eager updates
--- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent 18009fe commit d59fddd

File tree

2 files changed

+191
-41
lines changed
  • lib/node_modules/@stdlib/plot/base/view/lib/browser/routes

2 files changed

+191
-41
lines changed

lib/node_modules/@stdlib/plot/base/view/lib/browser/routes/app/app.js

Lines changed: 175 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,18 @@
2222

2323
// eslint-disable-next-line no-restricted-syntax
2424
(function iife() {
25-
var throttledRerender; // Function for re-rendering a visualization
26-
var editorConfig; // Object used for storing an editor configuration
27-
var TOGGLE_STATE; // Current editor menu toggle state
28-
var ACK_INTERVAL; // Number of milliseconds between requests to acknowledge received events
29-
var eventSource; // Instance used to listen for server events
30-
var VERBOSE; // Flag to indicate whether the application should be "chatty" in logging events
31-
var editor; // Editor instance
32-
var schema; // Visualization schema
33-
var lastID; // Identifier of the last event received by the client
34-
var view; // Vega visualization view instance
35-
var ack; // Reference to an interval timer for performing periodic acknowledgment of received events
25+
var throttledRerender; // Function for re-rendering a visualization
26+
var editorConfig; // Object used for storing an editor configuration
27+
var TOGGLE_STATE; // Current editor menu toggle state
28+
var ACK_INTERVAL; // Number of milliseconds between requests to acknowledge received events
29+
var eventSource; // Instance used to listen for server events
30+
var rawSchema; // Raw visualization schema
31+
var VERBOSE; // Flag to indicate whether the application should be "chatty" in logging events
32+
var editor; // Editor instance
33+
var schema; // Schema used for rendering a visualization
34+
var lastID; // Identifier of the last event received by the client
35+
var view; // Vega visualization view instance
36+
var ack; // Reference to an interval timer for performing periodic acknowledgment of received events
3637

3738
VERBOSE = 1;
3839
lastID = -1;
@@ -175,6 +176,86 @@
175176
}
176177
}
177178

179+
/**
180+
* Returns an editor-specific signal name.
181+
*
182+
* ## Notes
183+
*
184+
* - The intent of this function is to namespace editor signals to avoid potential naming collisions.
185+
*
186+
* @private
187+
* @param {string} name - raw signal name
188+
* @returns {string} signal name belonging to the editor signal namespace
189+
*/
190+
function signalName( name ) {
191+
if (
192+
name === 'width' ||
193+
name === 'height' ||
194+
name === 'padding' ||
195+
name === 'autosize'
196+
) {
197+
return name;
198+
}
199+
return '_config_' + name.replace( '/', '_' );
200+
}
201+
202+
/**
203+
* Returns a deep copy of a JSON object.
204+
*
205+
* @private
206+
* @param {Object} obj - JSON object
207+
* @returns {Object} deep copy
208+
*/
209+
function copyJSON( obj ) {
210+
return JSON.parse( JSON.stringify( obj ) );
211+
}
212+
213+
/**
214+
* Adds Editor signals to the visualization specification.
215+
*
216+
* @private
217+
* @param {Object} spec - visualization specification
218+
* @returns {Object} updated specification
219+
*/
220+
function addEditorSignals( spec ) {
221+
var signals;
222+
223+
spec = copyJSON( spec );
224+
225+
signals = spec.signals || [];
226+
227+
// FIXME: if a value is a signal, we want the initial signal value. We can use the view.signal API (https://vega.github.io/vega/docs/api/view/#view_signal), but we first need to generate the data flow graph so that the value can be dynamically determined. So maybe what we do is we first render the "raw schema", then, upon setting up the editor, we create our new wired up schema, which we then use to re-render the visualization.
228+
229+
// FIXME: we need to determine how we want to handle values already having signals, as Vega does not explicitly allow for values to have multiple signal inputs. In principle, we could just blow them away, as we'll be replacing with our own, but a user may expect the default Vega behavior.
230+
231+
signals.push({
232+
'name': signalName( 'background' ),
233+
'value': spec.background || '#fff'
234+
});
235+
spec.background = {
236+
'signal': signalName( 'background' )
237+
};
238+
signals.push({
239+
'name': signalName( 'description' ),
240+
'value': spec.description || ''
241+
});
242+
spec.description = {
243+
'signal': signalName( 'description' )
244+
};
245+
246+
signals.push({
247+
'name': signalName( 'title/text' ),
248+
'value': spec.title.text || []
249+
});
250+
spec.title.text = {
251+
'signal': signalName( 'title/text' )
252+
};
253+
254+
spec.signals = signals;
255+
256+
return spec;
257+
}
258+
178259
/**
179260
* Renders a visualization.
180261
*
@@ -184,6 +265,7 @@
184265
*/
185266
function render( spec ) {
186267
view = new vega.View( vega.parse( spec ), {
268+
'logLevel': vega.Warn,
187269
'renderer': 'svg',
188270
'container': '#main',
189271
'hover': true
@@ -195,12 +277,34 @@
195277
* Re-renders a visualization.
196278
*
197279
* @private
280+
* @param {View} view - Vega view instance
198281
* @param {Object} spec - visualization specification
199-
* @returns {Promise} promise which resolves upon rendering a visualization
200282
*/
201-
function rerender( spec ) {
283+
function rerender( view, spec ) {
284+
log( 'Re-rendering visualization...' );
202285
view.finalize();
203-
return render( spec );
286+
render( spec )
287+
.then( onRender )
288+
.catch( onError );
289+
290+
/**
291+
* Callback invoked upon rendering a visualization.
292+
*
293+
* @private
294+
*/
295+
function onRender() {
296+
log( 'Successfully re-rendered visualization.' );
297+
}
298+
299+
/**
300+
* Callback invoked upon encountering an error.
301+
*
302+
* @private
303+
* @param {Error} error - error object
304+
*/
305+
function onError( error ) {
306+
console.log( 'Encountered an error when attempting to render visualization. Error: %s', error.message );
307+
}
204308
}
205309

206310
/**
@@ -235,8 +339,9 @@
235339
* @returns {Promise} promise which resolves upon rendering a visualization
236340
*/
237341
function onJSON( spec ) {
238-
schema = spec;
239-
return render( spec );
342+
rawSchema = spec;
343+
schema = addEditorSignals( rawSchema );
344+
return render( schema );
240345
}
241346

242347
/**
@@ -308,7 +413,10 @@
308413
// FIXME: depending on changes, we may need to re-initialize the editor
309414

310415
// FIXME: perform JSON patch (currently, we're receiving the entire schema)
311-
onRefresh( event ); // FIXME
416+
417+
// onRefresh( event ); // FIXME
418+
419+
// rawSchema = JSON.parse( event.data ); // FIXME
312420
}
313421

314422
/**
@@ -321,8 +429,11 @@
321429
function onRefresh( event ) {
322430
log( 'Received a "refresh" event.' );
323431
lastID = event.id;
324-
schema = JSON.parse( event.data );
325-
throttledRerender( schema );
432+
433+
rawSchema = JSON.parse( event.data );
434+
schema = addEditorSignals( rawSchema );
435+
436+
throttledRerender( view, schema );
326437

327438
// FIXME: this may need to be `reInitEditor` (e.g., if new axes/scales/datasets are added or axes/scales/datasets are removed, etc)
328439
refreshEditor();
@@ -394,10 +505,10 @@
394505
* @private
395506
*/
396507
function syncEditorConfigGeneral() {
397-
editorConfig.general.background = schema.background || '#fff';
398-
editorConfig.general.description = schema.description || '';
399-
editorConfig.general.height = schema.height;
400-
editorConfig.general.width = schema.width;
508+
editorConfig.general.background = rawSchema.background || '#fff';
509+
editorConfig.general.description = rawSchema.description || '';
510+
editorConfig.general.height = rawSchema.height;
511+
editorConfig.general.width = rawSchema.width;
401512
}
402513

403514
/**
@@ -406,10 +517,10 @@
406517
* @private
407518
*/
408519
function syncEditorConfigPadding() {
409-
editorConfig.padding.bottom = schema.padding.bottom || 0;
410-
editorConfig.padding.left = schema.padding.left || 0;
411-
editorConfig.padding.right = schema.padding.right || 0;
412-
editorConfig.padding.top = schema.padding.top || 0;
520+
editorConfig.padding.bottom = rawSchema.padding.bottom || 0;
521+
editorConfig.padding.left = rawSchema.padding.left || 0;
522+
editorConfig.padding.right = rawSchema.padding.right || 0;
523+
editorConfig.padding.top = rawSchema.padding.top || 0;
413524
}
414525

415526
/**
@@ -418,11 +529,11 @@
418529
* @private
419530
*/
420531
function syncEditorConfigTitle() {
421-
editorConfig.title.text = schema.title.text.join( '\n' );
422-
editorConfig.title.align = schema.title.align || '';
423-
editorConfig.title.fontSize = schema.title.fontSize || 0;
424-
editorConfig.title.fontStyle = schema.title.fontStyle || '';
425-
editorConfig.title.fontWeight = schema.title.fontWeight || '';
532+
editorConfig.title.text = rawSchema.title.text.join( '\n' );
533+
editorConfig.title.align = rawSchema.title.align || '';
534+
editorConfig.title.fontSize = rawSchema.title.fontSize || 0;
535+
editorConfig.title.fontStyle = rawSchema.title.fontStyle || '';
536+
editorConfig.title.fontWeight = rawSchema.title.fontWeight || '';
426537
}
427538

428539
/**
@@ -507,10 +618,10 @@
507618
var folder = editor.addFolder( 'Padding' );
508619
folder.close();
509620

510-
folder.add( editorConfig.padding, 'bottom', 0 );
511-
folder.add( editorConfig.padding, 'left', 0 );
512-
folder.add( editorConfig.padding, 'right', 0 );
513-
folder.add( editorConfig.padding, 'top', 0 );
621+
folder.add( editorConfig.padding, 'bottom' );
622+
folder.add( editorConfig.padding, 'left' );
623+
folder.add( editorConfig.padding, 'right' );
624+
folder.add( editorConfig.padding, 'top' );
514625
}
515626

516627
/**
@@ -560,17 +671,28 @@
560671
var opts;
561672
var url;
562673
var obj;
674+
var flg;
563675
var ns;
564676

565677
// FIXME: other properties/objects
566678

679+
// For height/width/padding updates, we avoid propagating changes to the in-memory specification, as these updates also require additional layout modifications (e.g., scale ranges) and the logic for that is on the server. Instead, after pushing these changes to the server, the server will respond with a "refresh" event which will force the visualization to be re-rendered...
567680
obj = event.object;
568681
switch ( obj ) {
569682
case editorConfig.general:
570683
ns = '';
684+
switch ( event.property ) {
685+
case 'width':
686+
case 'height':
687+
flg = true;
688+
break;
689+
default:
690+
break;
691+
}
571692
break;
572693
case editorConfig.padding:
573694
ns = 'padding/';
695+
flg = true;
574696
break;
575697
case editorConfig.title:
576698
ns = 'title/';
@@ -580,13 +702,19 @@
580702
}
581703
log( 'Editor changed: %s%s', ns, event.property );
582704

705+
if ( !flg ) {
706+
log( 'Updating rendered visualization...' );
707+
view.signal( signalName( ns+event.property ), event.value );
708+
view.runAsync()
709+
.then( onRender );
710+
}
583711
url = '/config/'+ns+event.property;
584712
opts = {
585713
'method': 'POST',
586714
'headers': {
587715
'Content-Type': 'text/plain'
588716
},
589-
'body': event.value,
717+
'body': event.value.toString(),
590718
'credentials': 'same-origin'
591719
};
592720

@@ -595,6 +723,15 @@
595723
.then( onResponse )
596724
.catch( onError );
597725

726+
/**
727+
* Callback invoked upon rendering a visualization.
728+
*
729+
* @private
730+
*/
731+
function onRender() {
732+
log( 'Successfully rendered visualization.' );
733+
}
734+
598735
/**
599736
* Callback invoked upon receiving an HTTP response.
600737
*
@@ -680,7 +817,7 @@
680817
function sendAcknowledgment() {
681818
var opts = {
682819
'method': 'POST',
683-
'body': lastID.toString(),
820+
'body': lastID.toString(), // FIXME: this isn't working
684821
'credentials': 'same-origin'
685822
};
686823
log( 'Sending acknowledgement to server...' );

lib/node_modules/@stdlib/plot/base/view/lib/browser/routes/events/main.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,22 @@ function handler( request, response ) {
8080
*/
8181
function onChange( event ) {
8282
var data;
83-
if ( event && event.source === 'data' ) {
84-
// TODO: handle data change event
85-
return;
83+
if ( event ) {
84+
if ( event.source === 'data' ) {
85+
// TODO: handle data change event
86+
87+
// NOTE: for data changes, will also need to propagate plot changes (e.g., scales ranges might need updating, etc)
88+
return;
89+
}
90+
// For height/width/padding changes, we need to push a fresh specification to the client, as these changes affect other properties in specifications (e.g., scale ranges)...
91+
if (
92+
event.property === 'width' ||
93+
event.property === 'height' ||
94+
event.source === 'padding'
95+
) {
96+
refresh();
97+
return;
98+
}
8699
}
87100
counter += 1;
88101

0 commit comments

Comments
 (0)