mascot-vis 1.12.0 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/mascot-min.d.ts +24350 -0
  2. package/dist/mascot-min.js +4 -4
  3. package/dist/mascot.js +1521 -1492
  4. package/package.json +5 -3
  5. package/scenes - toDel/AreaChart.msc +1 -0
  6. package/scenes - toDel/AreaChart2.msc +1 -0
  7. package/scenes - toDel/BarChartHorz.msc +1 -0
  8. package/scenes - toDel/BarChartVert.msc +1 -0
  9. package/scenes - toDel/BoxPlot.msc +1 -0
  10. package/scenes - toDel/BubblePlot.msc +1 -0
  11. package/scenes - toDel/BulletChart.msc +1 -0
  12. package/scenes - toDel/BumpChart.msc +1 -0
  13. package/scenes - toDel/CirclePacking.msc +1 -0
  14. package/scenes - toDel/ConnectedScatterPlot.msc +1 -0
  15. package/scenes - toDel/DensityPlot.msc +1 -0
  16. package/scenes - toDel/DivergingBarChart.msc +1 -0
  17. package/scenes - toDel/DotPlot.msc +1 -0
  18. package/scenes - toDel/DoughnutChart.msc +1 -0
  19. package/scenes - toDel/DumbbellChart.msc +1 -0
  20. package/scenes - toDel/GanttChart.msc +1 -0
  21. package/scenes - toDel/GridAreaChart.msc +1 -0
  22. package/scenes - toDel/GridStackedAreaChart.msc +1 -0
  23. package/scenes - toDel/GroupedBarChart.msc +1 -0
  24. package/scenes - toDel/HeatMap.msc +1 -0
  25. package/scenes - toDel/Histogram.msc +1 -0
  26. package/scenes - toDel/Isotype.msc +1 -0
  27. package/scenes - toDel/LineGraph.msc +1 -0
  28. package/scenes - toDel/LineGraph2.msc +1 -0
  29. package/scenes - toDel/LollipopChart.msc +1 -0
  30. package/scenes - toDel/MosaicPlot.msc +1 -0
  31. package/scenes - toDel/MultiLineGraph.msc +1 -0
  32. package/scenes - toDel/MultipleAreaCharts.msc +1 -0
  33. package/scenes - toDel/MultipleBarCharts.msc +1 -0
  34. package/scenes - toDel/MultipleBoxPlots.msc +1 -0
  35. package/scenes - toDel/MultiplePieCharts.msc +1 -0
  36. package/scenes - toDel/MultipleWaffleCharts.msc +1 -0
  37. package/scenes - toDel/ParallelCoordinates.msc +1 -0
  38. package/scenes - toDel/PieChart.msc +1 -0
  39. package/scenes - toDel/RadarChart.msc +1 -0
  40. package/scenes - toDel/RadialBarChart.msc +1 -0
  41. package/scenes - toDel/RangeChart.msc +1 -0
  42. package/scenes - toDel/RoseChart.msc +1 -0
  43. package/scenes - toDel/Scatterplot.msc +1 -0
  44. package/scenes - toDel/ScatterplotMatrix.msc +1 -0
  45. package/scenes - toDel/SlopeGraph.msc +1 -0
  46. package/scenes - toDel/Sparklines.msc +1 -0
  47. package/scenes - toDel/StackedAreaChart.msc +1 -0
  48. package/scenes - toDel/StackedBarChart.msc +1 -0
  49. package/scenes - toDel/StellarChart.msc +1 -0
  50. package/scenes - toDel/StreamGraph.msc +1 -0
  51. package/scenes - toDel/StringlineChart.msc +1 -0
  52. package/scenes - toDel/TowerChart.msc +1 -0
  53. package/scenes - toDel/Treemap.msc +1 -0
  54. package/scenes - toDel/ViolinPlot.msc +1 -0
  55. package/scenes - toDel/WaffleChart.msc +1 -0
  56. package/scenes - toDel/WaterfallChart.msc +1 -0
  57. package/scenes - toDel/ridgelinePlot.msc +1 -0
  58. package/scenes - toDel/test.msc +1 -0
package/dist/mascot.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable */
2
- // version: 1.12.0
2
+ // version: 1.12.1
3
3
  (function (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3'), require('pixi.js')) :
5
5
  typeof define === 'function' && define.amd ? define(['exports', 'd3', 'pixi.js'], factory) :
@@ -257,7 +257,8 @@
257
257
  FEATURE_NOT_IMPLEMENTED: "This feature has not been implemented yet",
258
258
  LAYOUT_WITHOUT_TREE: "The layout can only be applied to a tree",
259
259
  UNSUPPORTED_FIELDTYPE: "Unsupported field type for encoding ",
260
- CANNOT_CLASSIFY: "Cannot classify items in "
260
+ CANNOT_CLASSIFY: "Cannot classify items in ",
261
+ CANNOT_CREATE_GLYPH: "Cannot create glyph. A glyph must consist of only paths or only texts, not a mixture of paths and texts"
261
262
  };
262
263
 
263
264
  const categoricalColorSchemes = [
@@ -272,1788 +273,1797 @@
272
273
  "schemeBlues", "schemeGreens", "schemeGreys", "schemeOranges", "schemePurples", "schemeReds", "schemeBuGn", "schemeBuPu", "schemeGnBu", "schemeOrRd", "schemePuBuGn", "schemePuBu", "schemePuRd", "schemeRdPu", "schemeYlGnBu", "schemeYlGn", "schemeYlOrBr", "schemeYlOrRd"
273
274
  ];
274
275
 
275
- class Layout {
276
+ class LinearGradient {
277
+
278
+ constructor(args) {
279
+ this._stops = [];
280
+ this.type = ItemType.LinearGradient;
281
+ this.id = this.type + ItemCounter[this.type]++;
282
+ this.x1 = ("x1" in args) ? args.x1 : 0;
283
+ this.x2 = ("x2" in args) ? args.x2 : 100;
284
+ this.y1 = ("y1" in args) ? args.y1 : 0;
285
+ this.y2 = ("y2" in args) ? args.y2 : 0;
286
+ }
287
+
288
+ toJSON() {
289
+ let json = {};
290
+ json.type = this.type;
291
+ json.id = this.id;
292
+ json.x1 = this.x1;
293
+ json.x2 = this.x2;
294
+ json.y1 = this.y1;
295
+ json.y2 = this.y2;
296
+ json.stops = this._stops;
297
+ return json;
298
+ }
276
299
 
277
- constructor(args){
278
- this.group = undefined;
300
+ addStop(offset, color, opacity) {
301
+ this._stops.push({offset: offset, color: color, opacity: opacity});
279
302
  }
280
303
 
281
- run(){}
304
+ get stops() {
305
+ return this._stops;
306
+ }
282
307
 
283
- clone(){}
284
308
  }
285
309
 
286
- class GridLayout extends Layout {
310
+ // Based on item.Item.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
311
+
312
+ class Mark {
287
313
 
288
314
  constructor(args) {
289
- super();
290
- this.type = "grid";
291
- this._numCols = args["numCols"];
292
- this._numRows = args["numRows"];
293
- this._dir = ("dir" in args) ? args["dir"] : [GridLayout.direction.Left2Right, GridLayout.direction.Top2Bottom];
294
- // this._hDir = ("hDir" in args) ? args["hDir"] : GridLayout.direction.Left2Right;
295
- // this._vDir = ("vDir" in args) ? args["vDir"] : GridLayout.direction.Top2Bottom;
296
- this._rowGap = "rowGap" in args && args["rowGap"] !== undefined ? args["rowGap"] : 5;
297
- this._colGap = "colGap" in args && args["colGap"] !== undefined ? args["colGap"] : 5;
298
- this._cellHorzAlignment = "horzCellAlignment" in args && this._validateCellAlignment("h", args["horzCellAlignment"]) ? args["horzCellAlignment"] : Alignment.Left;
299
- this._cellVertAlignment = "vertCellAlignment" in args && this._validateCellAlignment("v", args["vertCellAlignment"]) ? args["vertCellAlignment"] : Alignment.Bottom;
300
- if (!this._numCols && !this._numRows)
301
- this._numRows = 1;
315
+ this._dataScope = undefined;
316
+ this._id = undefined;
317
+
318
+ this.attrs = {};
319
+ this.styles = {};
320
+ this.staticProperties = {};
321
+
322
+ if (args !== undefined) {
323
+ for (let s in Style2SVG) {
324
+ if (s in args) {
325
+ this.styles[s] = args[s];
326
+ }
327
+ }
328
+ }
302
329
  }
303
330
 
304
- _validateCellAlignment(orientation, v) {
305
- if (orientation === "h" && [Alignment.Left, Alignment.Center, Alignment.Right].indexOf(v) >= 0){
306
- return true;
307
- } else if (orientation === "v" && [Alignment.Top, Alignment.Middle, Alignment.Bottom].indexOf(v) >= 0){
308
- return true;
331
+ get id() {
332
+ return this._id;
333
+ }
334
+
335
+ set id(id) {
336
+ if (this.getScene()){
337
+ delete this.getScene()._itemMap[this._id];
338
+ this._id = id;
339
+ this.getScene()._itemMap[id] = this;
340
+ } else {
341
+ this._id = id;
309
342
  }
310
- console.warn("Invalid alignment:", v);
311
- return false;
343
+ }
312
344
 
345
+ //TODO: implement winding contribution, see paper.js PathItem.Boolean.js
346
+ contains(px, py) {
347
+ if (!this._bounds)
348
+ return false;
349
+ if (!this._bounds.contains(px, py))
350
+ return false;
351
+ switch (this.type) {
352
+ case ItemType.Rect:
353
+ case ItemType.PointText:
354
+ return true;
355
+ case ItemType.Circle: {
356
+ let dist = Math.sqrt(Math.pow(px - this.x, 2) + Math.pow(py - this.y, 2));
357
+ return dist <= this.radius + this.strokeWidth;
358
+ }
359
+ case ItemType.Path: {
360
+ let ctx = CanvasProvider.getContext(),
361
+ p = new Path2D(this.getSVGPathData());
362
+ ctx.lineWidth = Math.max(this.strokeWidth, 2.5);
363
+ ctx.stroke(p);
364
+ if (this.closed) {
365
+ return ctx.isPointInPath(p, px, py);
366
+ } else {
367
+ return ctx.isPointInStroke(p, px, py);
368
+ }
369
+ }
370
+ case ItemType.Line: {
371
+ let ctx = CanvasProvider.getContext(),
372
+ p = new Path2D(this.getSVGPathData());
373
+ ctx.lineWidth = Math.max(this.strokeWidth, 2.5);
374
+ ctx.stroke(p);
375
+ return ctx.isPointInStroke(p, px, py);
376
+ }
377
+ default: {
378
+ let ctx = CanvasProvider.getContext(),
379
+ p = new Path2D(this.getSVGPathData());
380
+ return ctx.isPointInPath(p, px, py);
381
+ }
382
+ }
313
383
  }
314
384
 
315
385
  toJSON() {
316
- let json = {args: {}};
386
+ let json = {};
317
387
  json.type = this.type;
318
- json.args.numCols = this._numCols;
319
- json.args.numRows = this._numRows;
320
- json.args.colGap = this._colGap;
321
- json.args.rowGap = this._rowGap;
322
- json.args.horzCellAlignment = this._cellHorzAlignment;
323
- json.args.vertCellAlignment = this._cellVertAlignment;
324
- json.left = this._left;
325
- json.top = this._top;
326
- json.args.dir = this._dir;
388
+ json.id = this.id;
389
+ if (this.classId)
390
+ json.classId = this.classId;
391
+ if (this._dataScope)
392
+ json.dataScope = this._dataScope.toJSON();
393
+ json.args = {};
394
+ for (let s in this.attrs) {
395
+ json.args[s] = this.attrs[s];
396
+ }
397
+
398
+ for (let s in this.styles) {
399
+ if (s.indexOf("Color") > 0 && this.styles[s] instanceof LinearGradient) {
400
+ json.args[s] = this.styles[s].toJSON();
401
+ } else {
402
+ json.args[s] = this.styles[s];
403
+ }
404
+ }
327
405
  return json;
328
406
  }
329
407
 
330
- clone() {
331
- return new GridLayout({
332
- numCols: this._numCols,
333
- numRows: this._numRows,
334
- // hDir: this._hDir,
335
- // vDir: this._vDir,
336
- dir: this._dir,
337
- colGap: this._colGap,
338
- rowGap: this._rowGap
339
- });
408
+ getScene() {
409
+ let p = this;
410
+ while (p) {
411
+ if (p.type == ItemType.Scene)
412
+ return p;
413
+ else
414
+ p = p.parent;
415
+ }
340
416
  }
341
417
 
342
- get cellBounds() {
343
- let numCols, numRows, group = this.group, colGap = this._colGap, rowGap = this._rowGap;
344
- if (this._numRows) {
345
- numRows = this._numRows;
346
- numCols = Math.ceil(this.group.children.length/this._numRows);
347
- } else if (this._numCols) {
348
- numCols = this._numCols;
349
- numRows = Math.ceil(this.group.children.length/this._numCols);
350
- }
418
+ set dataScope(ds) {
419
+ this._dataScope = ds;
420
+ }
351
421
 
352
- let bounds = group.children.map(d => d.bounds);
353
- if (this._left === undefined) {
354
- let lefts = bounds.map(d => d.left),
355
- tops = bounds.map(d => d.top);
356
- this._left = Math.min(...lefts);
357
- this._top = Math.min(...tops);
422
+ get dataScope() {
423
+ return this._dataScope;
424
+ }
425
+
426
+ duplicate() {
427
+ let scene = this.getScene();
428
+ let m = scene.mark(this.type);
429
+ this.copyPropertiesTo(m);
430
+ m.classId = this.classId;
431
+ if (this._dataScope) {
432
+ m._dataScope = this._dataScope.clone();
358
433
  }
434
+ return m;
435
+ }
359
436
 
360
- let wds = bounds.map(d => d.width),
361
- hts = bounds.map(d => d.height),
362
- cellWidth = Math.max(...wds),
363
- cellHeight = Math.max(...hts);
364
-
365
- //TODO: cell size should be determined by the scale range extent if bound to data
366
- //analyze the group's children to see if
367
-
368
- let xEncs = group.getInternalEncodings("x"),
369
- yEncs = group.getInternalEncodings("y"),
370
- wdEncs = group.getInternalEncodings("width"),
371
- htEncs = group.getInternalEncodings("height");
437
+ // eslint-disable-next-line no-unused-vars
438
+ _doTranslate(dx, dy){
439
+ //child classes have their own implementations
440
+ }
372
441
 
373
- let leftOffset = 0; //, topOffset = 0;
374
- if (xEncs.length > 0) {
375
- let xEnc = xEncs[xEncs.length -1];
376
- cellWidth = xEnc.scale.rangeExtent;
377
- leftOffset = xEnc.scale.range[0];
378
- if (xEnc.scale.type === "point") {
379
- //TODO: need to handle variable sizes
380
- cellWidth += xEnc.anyItem.bounds.width;
381
- }
382
- } else if (wdEncs.length > 0 && wdEncs[wdEncs.length -1]._rectNegativeValues) { //width encoding with negative values
383
- cellWidth = wdEncs[wdEncs.length -1].scale.rangeExtent;
384
- leftOffset = wdEncs[wdEncs.length -1].scale.range[0];
385
- }
386
- if (yEncs.length > 0) {
387
- let yEnc = yEncs[yEncs.length -1];
388
- cellHeight = yEnc.scale.rangeExtent;
389
- if (yEnc.scale.type === "point") {
390
- //TODO: need to handle variable sizes
391
- cellHeight += yEnc.anyItem.bounds.height;
392
- }
393
- } else if (htEncs.length > 0 && htEncs[htEncs.length -1]._rectNegativeValues) { //width encoding with negative values
394
- cellHeight = htEncs[htEncs.length -1].scale.rangeExtent;
395
- }
442
+ set visibility(v) {
443
+ this.styles["visibility"] = v;
444
+ }
396
445
 
397
- let cb = [], cellCount = numRows * numCols;
446
+ get visibility() {
447
+ if (!this.styles["visibility"])
448
+ return "visible";
449
+ return this.styles["visibility"];
450
+ }
398
451
 
399
- switch (this._dir[0]) {
400
- case GridLayout.direction.Left2Right:
401
- switch (this._dir[1]) {
402
- case GridLayout.direction.Top2Bottom:
403
- for (let i = 0; i < cellCount; i++) {
404
- cb.push(new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
405
- this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
406
- }
407
- break;
408
- // return group.children.map((d, i) => new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
409
- // this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
410
- case GridLayout.direction.Bottom2Top:
411
- for (let i = 0; i < cellCount; i++) {
412
- cb.push(new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
413
- this._top + (this.numRows - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
414
- }
415
- break;
416
- // return group.children.map((d, i) => new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
417
- // this._top + (this.numRows - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
418
- }
419
- break;
420
- case GridLayout.direction.Right2Left:
421
- switch (this._dir[1]) {
422
- case GridLayout.direction.Top2Bottom:
423
- for (let i = 0; i < cellCount; i++) {
424
- cb.push(new Rectangle(leftOffset + this._left + (numCols - 1) * (cellWidth + colGap) - (cellWidth + colGap) * (i%numCols),
425
- this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
426
- }
427
- break;
428
- // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (numCols - 1) * (cellWidth + colGap) - (cellWidth + colGap) * (i%numCols),
429
- // this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
430
- case GridLayout.direction.Bottom2Top: {
431
- for (let i = 0; i < cellCount; i++) {
432
- cb.push(new Rectangle(leftOffset + this._left + (numCols - 1 - i%numCols) * (cellWidth + colGap),
433
- this._top + (this.numRows - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
434
- }
435
- break;
436
- // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (numCols - 1 - i%numCols) * (cellWidth + colGap),
437
- // this._top + (this.numRows - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
438
- }
439
- }
440
- break;
441
- case GridLayout.direction.Top2Bottom:
442
- switch (this._dir[1]) {
443
- case GridLayout.direction.Left2Right:
444
- for (let i = 0; i < cellCount; i++) {
445
- cb.push(new Rectangle(leftOffset + this._left + (cellWidth + colGap) * Math.floor(i/this.numRows),
446
- this._top + (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
447
- }
448
- break;
449
- // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (cellWidth + colGap) * Math.floor(i/this.numRows),
450
- // this._top + (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
451
- case GridLayout.direction.Right2Left:
452
- for (let i = 0; i < cellCount; i++) {
453
- cb.push(new Rectangle(leftOffset + this._left + (cellWidth + colGap) * (this.numCols - 1) - (cellWidth + colGap) * Math.floor(i/this.numRows),
454
- this._top + (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
455
- }
456
- break;
457
- // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (cellWidth + colGap) * (this.numCols - 1) - (cellWidth + colGap) * Math.floor(i/this.numRows),
458
- // this._top + (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
459
- }
460
- break;
461
- case GridLayout.direction.Bottom2Top:
462
- switch (this._dir[1]) {
463
- case GridLayout.direction.Left2Right:
464
- for (let i = 0; i < cellCount; i++) {
465
- cb.push(new Rectangle(leftOffset + this._left + (cellWidth + colGap) * Math.floor(i/this.numRows),
466
- this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
467
- }
468
- break;
469
- // this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight)));
470
- // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (cellWidth + colGap) * Math.floor(i/this.numRows),
471
- // this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
472
- case GridLayout.direction.Right2Left:
473
- for (let i = 0; i < cellCount; i++) {
474
- cb.push(new Rectangle(leftOffset + this._left + (cellWidth + colGap) * (this.numCols - 1) - (cellWidth + colGap) * Math.floor(i/this.numRows),
475
- this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
476
- }
477
- break;
478
- // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (cellWidth + colGap) * (this.numCols - 1) - (cellWidth + colGap) * Math.floor(i/this.numRows),
479
- // this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
480
- }
481
- break;
482
- }
483
-
484
- return cb;
485
-
486
- // if (this._vDir == GridLayout.direction.Top2Bottom) {
487
- // if (this._hDir == GridLayout.direction.Left2Right) {
488
- // return group.children.map((d, i) => new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
489
- // this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
490
- // } else { //right to left
491
- // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (numCols - 1) * (cellWidth + colGap) - (cellWidth + colGap) * (i%numCols),
492
- // this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
493
- // }
494
- // } else {
495
- // let nr = Math.ceil(this.group.children.length/this._numCols);
496
- // if (this._hDir == GridLayout.direction.Left2Right) {
497
- // return group.children.map((d, i) => new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
498
- // this._top + (nr - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
499
- // } else {
500
- // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (numCols - 1 - i%numCols) * (cellWidth + colGap),
501
- // this._top + (nr - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
502
- // }
503
- // }
452
+ get opacity() {
453
+ if (!("opacity" in this.styles))
454
+ return 1;
455
+ return this.styles["opacity"];
504
456
  }
505
457
 
506
- run() {
507
- if (this.group == undefined|| !this.group.children || this.group.children.length === 0)
508
- return;
509
-
510
- let cellBounds = this.cellBounds;
458
+ set opacity(c) {
459
+ this.styles["opacity"] = c;
460
+ }
461
+ }
511
462
 
512
- let xEncs = this.group.getInternalEncodings("x"),
513
- yEncs = this.group.getInternalEncodings("y"),
514
- wdEncs = this.group.getInternalEncodings("width"),
515
- htEncs = this.group.getInternalEncodings("height");
516
- for (let i = 0; i < this.group.children.length; i++) {
517
- let c = this.group.children[i];
518
- let gridBound = cellBounds[i];
463
+ // Based on util.Numerical.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
464
+ // http://paperjs.org/
465
+ // Copyright (c) 2011 - 2019, Juerg Lehni & Jonathan Puckey
466
+ // http://scratchdisk.com/ & https://puckey.studio/
467
+ //
468
+ // Distributed under the MIT license. See LICENSE file for detail
469
+ //
470
+ // All rights reserved.
519
471
 
520
- let dx = gridBound.x - c.bounds.x,
521
- dy = gridBound.y - c.bounds.y;
522
- c._doTranslate(dx, dy);
472
+ /**
473
+ * A very small absolute value used to check if a value is very close to
474
+ * zero. The value should be large enough to offset any floating point
475
+ * noise, but small enough to be meaningful in computation in a nominal
476
+ * range (see MACHINE_EPSILON).
477
+ *
478
+ * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
479
+ * http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf
480
+ */
481
+ const EPSILON = 1e-12;
523
482
 
524
- //alignment in cell if c's position is not bound to data
525
- let cdx = 0, cdy = 0;
526
- if (xEncs.length == 0) {
527
- switch(this._cellHorzAlignment) {
528
- case Alignment.Left:
529
- cdx = gridBound.left - c.bounds.left;
530
- break;
531
- case Alignment.Center:
532
- cdx = gridBound.x - c.bounds.x;
533
- break;
534
- case Alignment.Right:
535
- cdx = gridBound.right - c.bounds.right;
536
- break;
537
- }
538
- }
539
-
540
- if (yEncs.length == 0) {
541
- switch(this._cellVertAlignment) {
542
- case Alignment.Top:
543
- cdy = gridBound.top - c.bounds.top;
544
- break;
545
- case Alignment.Middle:
546
- cdy = gridBound.y - c.bounds.y;
547
- break;
548
- case Alignment.Bottom:
549
- cdy = gridBound.bottom - c.bounds.bottom;
550
- break;
551
- }
552
- }
553
-
554
- c._doTranslate(cdx, cdy);
555
- }
483
+ /**
484
+ * The epsilon to be used when performing "trigonometric" checks, such
485
+ * as examining cross products to check for collinearity.
486
+ */
487
+ const TRIGONOMETRIC_EPSILON = 1e-8;
556
488
 
557
- if (xEncs.length > 0) {
558
- //if childrens' position bound to data, compute position using the scale
559
- for (let enc of xEncs)
560
- enc._apply();
561
- } else if (wdEncs.length > 0) {
562
- let enc = wdEncs[wdEncs.length-1];
563
- if (enc._rectNegativeValues){
564
- enc._apply();
565
- }
566
- }
489
+ /**
490
+ * Checks if the value is 0, within a tolerance defined by
491
+ * Numerical.EPSILON.
492
+ */
493
+ function isZero(val) {
494
+ return val >= -EPSILON && val <= EPSILON;
495
+ }
567
496
 
568
- if (yEncs.length > 0) {
569
- //yEncs[yEncs.length-1]._map();
570
- // yEncs[yEncs.length-1]._apply();
571
- for (let enc of yEncs)
572
- enc._apply();
573
- } else if (htEncs.length > 0) {
574
- let enc = htEncs[htEncs.length-1];
575
- if (enc._rectNegativeValues){
576
- enc._apply();
577
- }
578
- }
497
+ // Based on basic.Point.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
579
498
 
580
- this.group._updateBounds();
499
+ class Point {
500
+
501
+ constructor(x, y) {
502
+ this.x = x;
503
+ this.y = y;
581
504
  }
582
505
 
583
- //TODO: add a corresponding scene level operation, automatically relayout
584
- set rowGap(g) {
585
- this._rowGap = g;
586
- this.run();
587
- this.group.getScene()._relayoutAncestors(this.group);
506
+ transform(matrix) {
507
+ return matrix ? matrix._transformPoint(this) : this;
588
508
  }
589
509
 
590
- get rowGap() {
591
- return this._rowGap;
592
- }
510
+ negate() {
511
+ return new Point(-this.x, -this.y);
512
+ }
593
513
 
594
- set colGap(g) {
595
- this._colGap = g;
596
- this.run();
597
- this.group.getScene()._relayoutAncestors(this.group);
514
+ subtract(point) {
515
+ return new Point(this.x - point.x, this.y - point.y);
598
516
  }
599
517
 
600
- get colGap() {
601
- return this._colGap;
602
- }
518
+ isZero() {
519
+ return isZero(this.x) && isZero(this.y)
520
+ }
603
521
 
604
- set numCols(c) {
605
- if (c < 0 || c > this.group.children.length) {
606
- console.warn("Cannot set", c, "columns for", this.group.children.length, "items in grid");
607
- return;
608
- }
609
- this._numCols = c;
610
- this._numRows = Math.ceil(this.group.children.length/c);
611
- this.run();
612
- this.group.getScene()._relayoutAncestors(this.group);
613
- }
522
+ /**
523
+ * Checks if the vector represented by this point is collinear (parallel) to
524
+ * another vector.
525
+ *
526
+ * @param {Point} point the vector to check against
527
+ * @return {Boolean} {@true it is collinear}
528
+ */
529
+ isCollinear(point) {
530
+ let x1 = this.x,
531
+ y1 = this.y,
532
+ x2 = point.x,
533
+ y2 = point.y;
534
+ return Math.abs(x1 * y2 - y1 * x2) <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
535
+ * /*#=*/TRIGONOMETRIC_EPSILON;
536
+ }
614
537
 
615
- get numCols() {
616
- if (this._numCols) {
617
- return this._numCols;
618
- } else if (this._numRows) {
619
- return Math.ceil(this.group.children.length/this._numRows);
620
- } else {
621
- return 0;
622
- }
623
- }
538
+ }
624
539
 
625
- set numRows(c) {
626
- if (c < 0 || c > this.group.children.length) {
627
- console.warn("Cannot set", c, "rows for", this.group.children.length, "items in grid");
628
- return;
629
- }
630
- this._numRows = c;
631
- this._numCols = Math.ceil(this.group.children.length/c);
632
- this.run();
633
- this.group.getScene()._relayoutAncestors(this.group);
634
- }
540
+ // Based on path.Segment.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
635
541
 
542
+ class Vertex {
636
543
 
637
- get numRows() {
638
- if (this._numRows) {
639
- return this._numRows;
640
- } else if (this._numCols) {
641
- return Math.ceil(this.group.children.length/this._numCols);
642
- } else
643
- return 0;
544
+ //handles are relative to the point
545
+ constructor(point, parentMark, id) {
546
+ this.type = "vertex";
547
+ this._id = id;
548
+ this.x = point.x;
549
+ this.y = point.y;
550
+ this.dataScope = undefined;
551
+ this.parent = parentMark;
552
+
553
+ this.shape = undefined;
554
+ this.width = 0;
555
+ this.height = 0;
556
+ this.radius = 0;
557
+ this.fillColor = "#555";
558
+ this.opacity = 1;
559
+ this.strokeWidth = 0;
560
+ this.strokeColor = "#aaa";
561
+ this._polarAngle = undefined;
644
562
  }
645
563
 
646
- set vertCellAlignment(v) {
647
- if (v != Alignment.Top && v != Alignment.Bottom && v != Alignment.Middle) {
648
- throw Errors.UNKOWN_ALIGNMENT;
564
+ get bounds() {
565
+ switch(this.shape) {
566
+ case "rect":
567
+ return new Rectangle(this.x - this.width/2, this.y - this.height/2, this.width, this.height);
568
+ case "circle":
569
+ return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2);
570
+ default:
571
+ return new Rectangle(this.x - 0.5, this.y - 0.5, 1, 1);
649
572
  }
650
- this._cellVertAlignment = v;
651
- this.run();
652
573
  }
653
574
 
654
- get vertCellAlignment() {
655
- return this._cellVertAlignment;
575
+ get id() {
576
+ return this.parent.id + "_v_" + this._id;
656
577
  }
657
578
 
658
- set horzCellAlignment(h) {
659
- if (h != Alignment.Left && h != Alignment.Center && h != Alignment.Right) {
660
- throw Errors.UNKOWN_ALIGNMENT;
661
- }
662
- this._cellHorzAlignment = h;
663
- this.run();
579
+ toJSON() {
580
+ let json = {};
581
+ json.type = this.type;
582
+ json.id = this._id;
583
+ json.x = this.x;
584
+ json.y = this.y;
585
+ if (this.dataScope)
586
+ json.dataScope = this.dataScope.toJSON();
587
+ if (this._polarAngle !== undefined)
588
+ json.polarAngle = this._polarAngle;
589
+ json.shape = this.shape;
590
+ json.width = this.width;
591
+ json.height = this.height;
592
+ json.radius = this.radius;
593
+ json.fillColor = this.fillColor;
594
+ json.opacity = this.opacity;
595
+ json.strokeWidth = this.strokeWidth;
596
+ json.strokeColor = this.strokeColor;
597
+ return json;
664
598
  }
665
599
 
666
- get horzCellAlignment() {
667
- return this._cellHorzAlignment;
600
+ static fromJSON(json, parent) {
601
+ let v = new Vertex(json, parent, json.id);
602
+ if (json.dataScope)
603
+ v.dataScope = json.dataScope;
604
+ if ("polarAngle" in json)
605
+ v.polarAngle = json.polarAngle;
606
+ v.shape = json.shape;
607
+ v.width = json.width;
608
+ v.height = json.height;
609
+ v.radius = json.radius;
610
+ v.fillColor = json.fillColor;
611
+ v.opacity = json.opacity;
612
+ v.strokeWidth = json.strokeWidth;
613
+ v.strokeColor = json.strokeColor;
614
+ return v;
668
615
  }
669
616
 
670
- //accepts two formats: a two-element array, or a string
671
- set direction(d) {
672
- if (Array.isArray(d) && d.length === 2) {
673
- this._dir = d;
674
- } else {
675
- this._dir = d.split("_");
617
+ _doTranslate(dx, dy) {
618
+ this.x += dx;
619
+ this.y += dy;
620
+ }
621
+
622
+ _clone(parent) {
623
+ let v = new Vertex(new Point(this.x, this.y), parent, this._id);
624
+ if (this.dataScope) {
625
+ v.dataScope = this.dataScope.clone();
676
626
  }
677
- this.run();
627
+ v.shape = this.shape;
628
+ v.width = this.width;
629
+ v.height = this.height;
630
+ v.radius = this.radius;
631
+ v.fillColor = this.fillColor;
632
+ v.opacity = this.opacity;
633
+ v.strokeWidth = this.strokeWidth;
634
+ v.strokeColor = this.strokeColor;
635
+ return v;
678
636
  }
679
637
 
680
- get direction() {
681
- return this._dir.join("_");
638
+ set polarAngle(a) {
639
+ this._polarAngle = a;
640
+ }
641
+
642
+ get polarAngle() {
643
+ return this._polarAngle;
682
644
  }
683
645
  }
684
646
 
685
- GridLayout.direction = {
686
- Left2Right: "l2r",
687
- Right2Left: "r2l",
688
- Top2Bottom: "t2b",
689
- Bottom2Top: "b2t"
690
- };
647
+ Vertex.styles = ["vxShape", "vxWidth", "vxHeight", "vxRadius", "vxFillColor", "vxStrokeColor", "vxStrokeWidth", "vxOpacity"];
691
648
 
692
- class LinearGradient {
649
+ class Segment {
693
650
 
694
- constructor(args) {
695
- this._stops = [];
696
- this.type = ItemType.LinearGradient;
697
- this.id = this.type + ItemCounter[this.type]++;
698
- this.x1 = ("x1" in args) ? args.x1 : 0;
699
- this.x2 = ("x2" in args) ? args.x2 : 100;
700
- this.y1 = ("y1" in args) ? args.y1 : 0;
701
- this.y2 = ("y2" in args) ? args.y2 : 0;
651
+ constructor(v1, v2, parentMark, id) {
652
+ this.type = "segment";
653
+ this._id = id;
654
+ this.vertex1 = v1;
655
+ this.vertex2 = v2;
656
+
657
+ this.dataScope = undefined;
658
+ this.parent = parentMark;
702
659
  }
703
-
704
- toJSON() {
705
- let json = {};
706
- json.type = this.type;
707
- json.id = this.id;
708
- json.x1 = this.x1;
709
- json.x2 = this.x2;
710
- json.y1 = this.y1;
711
- json.y2 = this.y2;
712
- json.stops = this._stops;
713
- return json;
714
- }
715
660
 
716
- addStop(offset, color, opacity) {
717
- this._stops.push({offset: offset, color: color, opacity: opacity});
718
- }
661
+ get id() {
662
+ return this.parent.id + "_s_" + this._id;
663
+ }
719
664
 
720
- get stops() {
721
- return this._stops;
722
- }
665
+ _doTranslate(dx, dy) {
666
+ this.vertex1._doTranslate(dx, dy);
667
+ this.vertex2._doTranslate(dx, dy);
668
+ }
723
669
 
670
+ get x() {
671
+ return (this.vertex1.x + this.vertex2.x)/2;
672
+ }
673
+
674
+ get y() {
675
+ return (this.vertex1.y + this.vertex2.y)/2;
676
+ }
724
677
  }
725
678
 
726
- // Based on item.Item.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
679
+ class Path extends Mark {
680
+
681
+ constructor(args) {
682
+ super(args);
683
+ this.type = "type" in args ? args.type : ItemType.Path;
727
684
 
728
- class Mark {
685
+ if (!("strokeColor" in this.styles))
686
+ this.styles["strokeColor"] = "#ccc";
687
+ if (!("strokeWidth" in this.styles))
688
+ this.styles["strokeWidth"] = 1;
689
+ if (!("strokeDash" in this.styles))
690
+ this.styles["strokeDash"] = "none";
729
691
 
730
- constructor(args) {
731
- this._dataScope = undefined;
732
- this._id = undefined;
692
+ this.vertices = [];
693
+ this.vertexCounter = 0; //for assigning vertex ids
694
+ this.segmentCounter = 0;
695
+ this.segments = [];
733
696
 
734
- this.attrs = {};
735
- this.styles = {};
736
- this.staticProperties = {};
697
+ this.anchor = undefined;
698
+
699
+ this.closed = false;
700
+
701
+ this.curveMode = "linear";
702
+
703
+ //when a path encodes data using its width or height, its geometric bounds may not be the same as its orginal bounds without encoding applied
704
+ this.boundsOffsets = {top: 0, bottom: 0, left: 0, right: 0};
705
+
706
+ this._vxShape = undefined;
707
+ this._vxWidth = 0;
708
+ this._vxHeight = 0;
709
+ this._vxRadius = 0;
710
+ this._vxFillColor = "#555555";
711
+ this._vxStrokeColor = "#aaaaaa";
712
+ this._vxStrokeWidth = 0;
713
+ this._vxOpacity = 1;
737
714
 
738
715
  if (args !== undefined) {
739
- for (let s in Style2SVG) {
740
- if (s in args) {
741
- this.styles[s] = args[s];
742
- }
716
+ for (let vs of Vertex.styles){
717
+ if (vs in args)
718
+ this["_" + vs] = args[vs];
743
719
  }
744
- }
745
- }
746
720
 
747
- get id() {
748
- return this._id;
749
- }
750
-
751
- set id(id) {
752
- if (this.getScene()){
753
- delete this.getScene()._itemMap[this._id];
754
- this._id = id;
755
- this.getScene()._itemMap[id] = this;
756
- } else {
757
- this._id = id;
721
+ if ("vertices" in args) {
722
+ this._setVertices(args["vertices"]);
723
+ }
758
724
  }
759
725
  }
760
726
 
761
- //TODO: implement winding contribution, see paper.js PathItem.Boolean.js
762
- contains(px, py) {
763
- if (!this._bounds)
764
- return false;
765
- if (!this._bounds.contains(px, py))
766
- return false;
727
+ toJSON() {
728
+ let json = super.toJSON();
729
+ json.type = this.type;
730
+ json.id = this.id;
767
731
  switch (this.type) {
768
732
  case ItemType.Rect:
769
- case ItemType.PointText:
770
- return true;
771
- case ItemType.Circle: {
772
- let dist = Math.sqrt(Math.pow(px - this.x, 2) + Math.pow(py - this.y, 2));
773
- return dist <= this.radius + this.strokeWidth;
774
- }
775
- case ItemType.Path: {
776
- let ctx = CanvasProvider.getContext(),
777
- p = new Path2D(this.getSVGPathData());
778
- ctx.lineWidth = Math.max(this.strokeWidth, 2.5);
779
- ctx.stroke(p);
780
- if (this.closed) {
781
- return ctx.isPointInPath(p, px, py);
782
- } else {
783
- return ctx.isPointInStroke(p, px, py);
733
+ json.args.width = this.width;
734
+ json.args.height = this.height;
735
+ json.args.top = this.top;
736
+ json.args.left = this.left;
737
+ break;
738
+ case ItemType.Circle:
739
+ json.args.x = this.x;
740
+ json.args.y = this.y;
741
+ json.args.radius = this.radius;
742
+ break;
743
+ case ItemType.Arc:
744
+ case ItemType.Pie:
745
+ json.args.x = this._x;
746
+ json.args.y = this._y;
747
+ json.args.innerRadius = this._innerRadius;
748
+ json.args.outerRadius = this._outerRadius;
749
+ json.args.startAngle = this._startAngle;
750
+ json.args.endAngle = this._endAngle;
751
+ break;
752
+ default:
753
+ json.vertices = [];
754
+ for (let v of this.vertices)
755
+ json.vertices.push(v.toJSON());
756
+ if (this.type === ItemType.Polygon) {
757
+ json.args.x = this._x;
758
+ json.args.y = this._y;
759
+ json.args.radius = this._radius;
760
+ } else if (this.type === ItemType.Area) {
761
+ json.args.baseline = this._baseline;
762
+ json.args.orientation = this._orientation;
784
763
  }
764
+ break;
765
+ }
766
+ // if (this.type === ItemType.Rect) {
767
+ // json.args.width = this.width;
768
+ // json.args.height = this.height;
769
+ // json.args.top = this.top;
770
+ // json.args.left = this.left;
771
+ // } else if (this.type === ItemType.Circle) {
772
+ // json.args.x = this.x;
773
+ // json.args.y = this.y;
774
+ // json.args.radius = this.radius;
775
+ // } else if (this.type === ItemType.Arc) {
776
+ // json.args.x = this._x;
777
+ // json.args.y = this._y;
778
+ // json.args.innerRadius = this._innerRadius;
779
+ // json.args.outerRadius = this._outerRadius;
780
+ // json.args.startAngle = this._startAngle;
781
+ // json.args.endAngle = this._endAngle;
782
+ // } else if (this.type === ItemType.Pie) {
783
+ // json.args.x = this._x;
784
+ // json.args.y = this._y;
785
+ // json.args.radius = this.radius;
786
+ // json.args.startAngle = this.startAngleDeg;
787
+ // json.args.endAngle = this.endAngleDeg;
788
+ // } else {
789
+ // json.vertices = [];
790
+ // for (let v of this.vertices)
791
+ // json.vertices.push(v.toJSON());
792
+ // if (this.type === ItemType.Polygon) {
793
+ // json.args.x = this._x;
794
+ // json.args.y = this._y;
795
+ // json.args.radius = this._radius;
796
+ // } else if (this.type === ItemType.Area) {
797
+ // json.args.baseline = this._baseline;
798
+ // json.args.orientation = this._orientation;
799
+ // }
800
+ // }
801
+ json.vertexCounter = this.vertexCounter;
802
+ json.segmentCounter = this.segmentCounter;
803
+ //do not save segments, anchor and closed for now
804
+ json.curveMode = this.curveMode;
805
+ if (this._bounds)
806
+ json.bounds = this._bounds.toJSON();
807
+ json.boundsOffsets = this.boundsOffsets;
808
+ for (let s of Vertex.styles) {
809
+ json.args[s] = this[s];
810
+ }
811
+ return json;
812
+ }
813
+
814
+ _setVertices(vertices) {
815
+ let vertex, point;
816
+ this.vertices = [];
817
+ this.segments = [];
818
+ for (let i = 0; i < vertices.length; i++) {
819
+
820
+ if (i == vertices.length - 1 && vertices[i][0] === vertices[0][0] && vertices[i][1] === vertices[0][1] && this.type === ItemType.Path) {
821
+ continue;
785
822
  }
786
- case ItemType.Line: {
787
- let ctx = CanvasProvider.getContext(),
788
- p = new Path2D(this.getSVGPathData());
789
- ctx.lineWidth = Math.max(this.strokeWidth, 2.5);
790
- ctx.stroke(p);
791
- return ctx.isPointInStroke(p, px, py);
792
- }
793
- default: {
794
- let ctx = CanvasProvider.getContext(),
795
- p = new Path2D(this.getSVGPathData());
796
- return ctx.isPointInPath(p, px, py);
823
+
824
+ point = new Point(vertices[i][0], vertices[i][1]);
825
+
826
+ vertex = new Vertex(point, this, this.vertexCounter++);
827
+
828
+ for (let vs of Vertex.styles){
829
+ if (this[vs]){
830
+ let temp = vs.replace("vx", "");
831
+ vertex[temp[0].toLowerCase() + temp.slice(1)] = this[vs];
832
+ }
797
833
  }
834
+
835
+ this.vertices.push(vertex);
836
+ if (i > 0)
837
+ this.segments.push(new Segment(this.vertices[i-1], this.vertices[i], this, this.segmentCounter++));
838
+ }
839
+ //if the first vertex has the same position as the last, this path is closed
840
+ let first = vertices[0], last = vertices[vertices.length - 1];
841
+ if ((first[0] === last[0] && first[1] === last[1]) || this.type === ItemType.Rect) {
842
+ this.closed = true;
843
+ if (!("fillColor" in this.styles))
844
+ this.styles["fillColor"] = "#fff";
845
+ this.segments.push(new Segment(this.vertices[this.vertices.length-1], this.vertices[0], this, this.segmentCounter++));
798
846
  }
799
847
  }
800
848
 
801
- toJSON() {
802
- let json = {};
803
- json.type = this.type;
804
- json.id = this.id;
805
- if (this.classId)
806
- json.classId = this.classId;
849
+ copyPropertiesTo(target) {
850
+ target.attrs = Object.assign({}, this.attrs);
851
+ target.styles = Object.assign({}, this.styles);
852
+ for (let vs of Vertex.styles){
853
+ if (this["_"+vs])
854
+ target["_"+vs] = this["_"+vs];
855
+ }
807
856
  if (this._dataScope)
808
- json.dataScope = this._dataScope.toJSON();
809
- json.args = {};
810
- for (let s in this.attrs) {
811
- json.args[s] = this.attrs[s];
857
+ target._dataScope = this._dataScope.clone();
858
+ target.closed = this.closed;
859
+ target.curveMode = this.curveMode;
860
+ target.vertices = [];
861
+ target.segments = [];
862
+ for (let v of this.vertices) {
863
+ target.vertices.push(v._clone(target));
812
864
  }
813
-
814
- for (let s in this.styles) {
815
- if (s.indexOf("Color") > 0 && this.styles[s] instanceof LinearGradient) {
816
- json.args[s] = this.styles[s].toJSON();
817
- } else {
818
- json.args[s] = this.styles[s];
819
- }
865
+ target.segmentCounter = 0;
866
+ for (let i = 1; i < target.vertices.length; i++) {
867
+ target.segments.push(new Segment(target.vertices[i-1], target.vertices[i], target, target.segmentCounter++));
820
868
  }
821
- return json;
869
+ if (target.closed)
870
+ target.segments.push(new Segment(target.vertices[target.vertices.length-1], target.vertices[0], target, target.segmentCounter++));
822
871
  }
823
872
 
824
- getScene() {
825
- let p = this;
826
- while (p) {
827
- if (p.type == ItemType.Scene)
828
- return p;
829
- else
830
- p = p.parent;
831
- }
873
+ /*
874
+ * returns the bounds without incorporating transformations involving rotation
875
+ */
876
+ get bounds() {
877
+ if (!this._bounds)
878
+ this._updateBounds();
879
+ return this._bounds;
832
880
  }
833
881
 
834
- set dataScope(ds) {
835
- this._dataScope = ds;
882
+ /**
883
+ * return the bounds as if the path does not encode data
884
+ */
885
+ get refBounds() {
886
+ if (!this._bounds)
887
+ this._updateBounds();
888
+ let ht = (this._bounds.bottom + this.boundsOffsets.bottom) - (this._bounds.top - this.boundsOffsets.top),
889
+ wd = this._bounds.right + this.boundsOffsets.right - (this._bounds.left - this.boundsOffsets.left);
890
+ return new Rectangle(this._bounds.left - this.boundsOffsets.left, this._bounds.top - this.boundsOffsets.top, wd, ht);
836
891
  }
837
892
 
838
- get dataScope() {
839
- return this._dataScope;
893
+ get x() {
894
+ return this.bounds.x;
840
895
  }
841
896
 
842
- duplicate() {
843
- let scene = this.getScene();
844
- let m = scene.mark(this.type);
845
- this.copyPropertiesTo(m);
846
- m.classId = this.classId;
847
- if (this._dataScope) {
848
- m._dataScope = this._dataScope.clone();
849
- }
850
- return m;
897
+ get y() {
898
+ return this.bounds.y;
851
899
  }
852
900
 
853
- // eslint-disable-next-line no-unused-vars
854
- _doTranslate(dx, dy){
855
- //child classes have their own implementations
901
+ get strokeColor() {
902
+ return this.styles["strokeColor"];
856
903
  }
857
904
 
858
- set visibility(v) {
859
- this.styles["visibility"] = v;
905
+ set strokeColor(c) {
906
+ this.styles["strokeColor"] = c;
860
907
  }
861
908
 
862
- get visibility() {
863
- if (!this.styles["visibility"])
864
- return "visible";
865
- return this.styles["visibility"];
909
+ get strokeWidth() {
910
+ return this.styles["strokeWidth"];
866
911
  }
867
912
 
868
- get opacity() {
869
- if (!("opacity" in this.styles))
870
- return 1;
871
- return this.styles["opacity"];
913
+ set strokeWidth(c) {
914
+ this.styles["strokeWidth"] = c;
872
915
  }
873
916
 
874
- set opacity(c) {
875
- this.styles["opacity"] = c;
917
+ get fillColor() {
918
+ return this.styles["fillColor"];
876
919
  }
877
- }
878
920
 
879
- // Based on util.Numerical.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
880
- // http://paperjs.org/
881
- // Copyright (c) 2011 - 2019, Juerg Lehni & Jonathan Puckey
882
- // http://scratchdisk.com/ & https://puckey.studio/
883
- //
884
- // Distributed under the MIT license. See LICENSE file for detail
885
- //
886
- // All rights reserved.
921
+ set fillColor(c) {
922
+ this.styles["fillColor"] = c;
923
+ }
887
924
 
888
- /**
889
- * A very small absolute value used to check if a value is very close to
890
- * zero. The value should be large enough to offset any floating point
891
- * noise, but small enough to be meaningful in computation in a nominal
892
- * range (see MACHINE_EPSILON).
893
- *
894
- * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
895
- * http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf
896
- */
897
- const EPSILON = 1e-12;
925
+ get strokeDash() {
926
+ return this.styles["strokeDash"];
927
+ }
898
928
 
899
- /**
900
- * The epsilon to be used when performing "trigonometric" checks, such
901
- * as examining cross products to check for collinearity.
902
- */
903
- const TRIGONOMETRIC_EPSILON = 1e-8;
929
+ set strokeDash(c) {
930
+ this.styles["strokeDash"] = c;
931
+ }
904
932
 
905
- /**
906
- * Checks if the value is 0, within a tolerance defined by
907
- * Numerical.EPSILON.
908
- */
909
- function isZero(val) {
910
- return val >= -EPSILON && val <= EPSILON;
911
- }
933
+ _doTranslate(dx, dy) {
934
+ for (let v of this.vertices) {
935
+ v._doTranslate(dx, dy);
936
+ }
937
+ this._updateBounds();
938
+ }
912
939
 
913
- // Based on basic.Point.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
940
+ //by default, with respect to the center of bounds
941
+ resize(wd, ht, xRef, yRef) {
942
+ let bounds = this.bounds, bWidth = bounds.width === 0 ? 1 : bounds.width, bHeight = bounds.height === 0 ? 1 : bounds.height;
943
+ if (xRef === "right") {
944
+ for (let v of this.vertices) {
945
+ v.x = bounds.right - (wd/bWidth) * (bounds.right - v.x);
946
+ }
947
+ } else {
948
+ for (let v of this.vertices) {
949
+ v.x = bounds.left + (wd/bWidth) * (v.x - bounds.left);
950
+ }
951
+ }
952
+ if (yRef === "top") {
953
+ for (let v of this.vertices) {
954
+ v.y = bounds.top + (ht/bHeight) * (v.y - bounds.top);
955
+ }
956
+ } else {
957
+ for (let v of this.vertices) {
958
+ v.y = bounds.bottom - (ht/bHeight) * (bounds.bottom - v.y);
959
+ }
960
+ }
961
+ this._updateBounds();
962
+ }
914
963
 
915
- class Point {
916
-
917
- constructor(x, y) {
918
- this.x = x;
919
- this.y = y;
964
+ _updateBounds() {
965
+ let vx = this.vertices.map(d => d.x),
966
+ vy = this.vertices.map(d => d.y);
967
+
968
+ let left = Math.min(...vx), top = Math.min(...vy), right = Math.max(...vx), btm = Math.max(...vy);
969
+
970
+ this._bounds = new Rectangle(left, top, right - left, btm - top);
971
+ if (this.type === ItemType.Line || this.type === ItemType.Path) {
972
+ let sw = this.styles["strokeWidth"] ? this.styles["strokeWidth"] : 1;
973
+ if (left === right)
974
+ this._bounds = new Rectangle(left - sw/2, top, right - left + sw, btm - top);
975
+ else if (top === btm)
976
+ this._bounds = new Rectangle(left, top - sw/2, right - left, btm - top + sw);
977
+ }
920
978
  }
921
979
 
922
- transform(matrix) {
923
- return matrix ? matrix._transformPoint(this) : this;
980
+ addVertex(x, y, i) {
981
+ let vertex = new Vertex(new Point(x, y), this, this.vertexCounter++);
982
+ this.vertices.splice(i, 0, vertex);
983
+ //TODO: handle segments
924
984
  }
925
985
 
926
- negate() {
927
- return new Point(-this.x, -this.y);
928
- }
986
+ sortVertices(channel, descending) {
987
+ this.vertices.sort((a,b) => a[channel] - b[channel]);
988
+ if (descending)
989
+ this.vertices.reverse();
990
+ for (let i = 0; i < this.segments.length; i++) {
991
+ let segment = this.segments[i];
992
+ segment.vertex1 = this.vertices[i];
993
+ segment.vertex2 = this.vertices[(i+1)%this.vertices.length];
994
+ }
995
+ }
929
996
 
930
- subtract(point) {
931
- return new Point(this.x - point.x, this.y - point.y);
997
+ sortVerticesByData(field, descending, order) {
998
+ let f;
999
+ if (order)
1000
+ f = (a, b) => order.indexOf(a.dataScope.getFieldValue(field)) - order.indexOf(b.dataScope.getFieldValue(field));
1001
+ else
1002
+ f = (a, b) => (a.dataScope.getFieldValue(field) < b.dataScope.getFieldValue(field) ? -1 : 1 );
1003
+ this.vertices.sort(f);
1004
+ if (descending)
1005
+ this.vertices.reverse();
1006
+ for (let i = 0; i < this.segments.length; i++) {
1007
+ let segment = this.segments[i];
1008
+ segment.vertex1 = this.vertices[i];
1009
+ segment.vertex2 = this.vertices[(i+1)%this.vertices.length];
1010
+ }
932
1011
  }
933
1012
 
934
- isZero() {
935
- return isZero(this.x) && isZero(this.y)
936
- }
1013
+ getSVGPathData() {
1014
+ let p = d3__namespace.path();
1015
+ let curve = this._getD3CurveFunction(this.curveMode)(p);
1016
+ curve.lineStart();
1017
+ for (let vertex of this.vertices) {
1018
+ curve.point(vertex.x, vertex.y);
1019
+ }
1020
+ if (this.closed)
1021
+ curve.point(this.vertices[0].x, this.vertices[0].y);
1022
+ curve.lineEnd();
937
1023
 
938
- /**
939
- * Checks if the vector represented by this point is collinear (parallel) to
940
- * another vector.
941
- *
942
- * @param {Point} point the vector to check against
943
- * @return {Boolean} {@true it is collinear}
944
- */
945
- isCollinear(point) {
946
- let x1 = this.x,
947
- y1 = this.y,
948
- x2 = point.x,
949
- y2 = point.y;
950
- return Math.abs(x1 * y2 - y1 * x2) <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
951
- * /*#=*/TRIGONOMETRIC_EPSILON;
952
- }
1024
+ return p._;
1025
+ }
1026
+
1027
+ // toSVG() {
953
1028
 
954
- }
1029
+ // }
955
1030
 
956
- // Based on path.Segment.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
1031
+ // fromSVG() {
957
1032
 
958
- class Vertex {
1033
+ // }
959
1034
 
960
- //handles are relative to the point
961
- constructor(point, parentMark, id) {
962
- this.type = "vertex";
963
- this._id = id;
964
- this.x = point.x;
965
- this.y = point.y;
966
- this.dataScope = undefined;
967
- this.parent = parentMark;
1035
+ get firstVertex() {
1036
+ return this.vertices[0];
1037
+ }
968
1038
 
969
- this.shape = undefined;
970
- this.width = 0;
971
- this.height = 0;
972
- this.radius = 0;
973
- this.fillColor = "#555";
974
- this.opacity = 1;
975
- this.strokeWidth = 0;
976
- this.strokeColor = "#aaa";
977
- this._polarAngle = undefined;
1039
+ get firstSegment() {
1040
+ return this.segments[0];
978
1041
  }
979
1042
 
980
- get bounds() {
981
- switch(this.shape) {
982
- case "rect":
983
- return new Rectangle(this.x - this.width/2, this.y - this.height/2, this.width, this.height);
984
- case "circle":
985
- return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2);
1043
+ get lastVertex() {
1044
+ return this.vertices[this.vertices.length - 1];
1045
+ }
1046
+
1047
+ get lastSegment() {
1048
+ return this.segments[this.segments.length - 1];
1049
+ }
1050
+
1051
+ _getD3CurveFunction(v){
1052
+ switch(v) {
1053
+ case CurveMode.Natural:
1054
+ return d3__namespace.curveNatural;
1055
+ case CurveMode.Basis:
1056
+ return d3__namespace.curveBasis;
1057
+ case CurveMode.BumpX:
1058
+ return d3__namespace.curveBumpX;
1059
+ case CurveMode.BumpY:
1060
+ return d3__namespace.curveBumpY;
1061
+ case CurveMode.Linear:
1062
+ return d3__namespace.curveLinear;
1063
+ case CurveMode.Step:
1064
+ return d3__namespace.curveStep;
1065
+ case CurveMode.CatmullRom:
1066
+ return d3__namespace.curveCatmullRom;
1067
+ case CurveMode.Cardinal:
1068
+ return d3__namespace.curveCardinal;
986
1069
  default:
987
- return new Rectangle(this.x - 0.5, this.y - 0.5, 1, 1);
1070
+ return d3__namespace.curveLinear;
988
1071
  }
989
1072
  }
990
1073
 
991
- get id() {
992
- return this.parent.id + "_v_" + this._id;
1074
+ get vxShape(){
1075
+ return this._vxShape;
993
1076
  }
994
1077
 
995
- toJSON() {
996
- let json = {};
997
- json.type = this.type;
998
- json.id = this._id;
999
- json.x = this.x;
1000
- json.y = this.y;
1001
- if (this.dataScope)
1002
- json.dataScope = this.dataScope.toJSON();
1003
- if (this._polarAngle !== undefined)
1004
- json.polarAngle = this._polarAngle;
1005
- json.shape = this.shape;
1006
- json.width = this.width;
1007
- json.height = this.height;
1008
- json.radius = this.radius;
1009
- json.fillColor = this.fillColor;
1010
- json.opacity = this.opacity;
1011
- json.strokeWidth = this.strokeWidth;
1012
- json.strokeColor = this.strokeColor;
1013
- return json;
1078
+ set vxShape(s){
1079
+ this._vxShape = s;
1080
+ for (let v of this.vertices)
1081
+ v.shape = s;
1014
1082
  }
1015
1083
 
1016
- static fromJSON(json, parent) {
1017
- let v = new Vertex(json, parent, json.id);
1018
- if (json.dataScope)
1019
- v.dataScope = json.dataScope;
1020
- if ("polarAngle" in json)
1021
- v.polarAngle = json.polarAngle;
1022
- v.shape = json.shape;
1023
- v.width = json.width;
1024
- v.height = json.height;
1025
- v.radius = json.radius;
1026
- v.fillColor = json.fillColor;
1027
- v.opacity = json.opacity;
1028
- v.strokeWidth = json.strokeWidth;
1029
- v.strokeColor = json.strokeColor;
1030
- return v;
1084
+ get vxWidth(){
1085
+ return this._vxWidth;
1031
1086
  }
1032
1087
 
1033
- _doTranslate(dx, dy) {
1034
- this.x += dx;
1035
- this.y += dy;
1088
+ set vxWidth(s){
1089
+ this._vxWidth = s;
1090
+ for (let v of this.vertices)
1091
+ v.width = s;
1036
1092
  }
1037
1093
 
1038
- _clone(parent) {
1039
- let v = new Vertex(new Point(this.x, this.y), parent, this._id);
1040
- if (this.dataScope) {
1041
- v.dataScope = this.dataScope.clone();
1042
- }
1043
- v.shape = this.shape;
1044
- v.width = this.width;
1045
- v.height = this.height;
1046
- v.radius = this.radius;
1047
- v.fillColor = this.fillColor;
1048
- v.opacity = this.opacity;
1049
- v.strokeWidth = this.strokeWidth;
1050
- v.strokeColor = this.strokeColor;
1051
- return v;
1094
+ get vxHeight(){
1095
+ return this._vxHeight;
1052
1096
  }
1053
1097
 
1054
- set polarAngle(a) {
1055
- this._polarAngle = a;
1098
+ set vxHeight(s){
1099
+ this._vxHeight = s;
1100
+ for (let v of this.vertices)
1101
+ v.height = s;
1056
1102
  }
1057
1103
 
1058
- get polarAngle() {
1059
- return this._polarAngle;
1104
+ get vxRadius(){
1105
+ return this._vxRadius;
1060
1106
  }
1061
- }
1062
1107
 
1063
- Vertex.styles = ["vxShape", "vxWidth", "vxHeight", "vxRadius", "vxFillColor", "vxStrokeColor", "vxStrokeWidth", "vxOpacity"];
1108
+ set vxRadius(s){
1109
+ this._vxRadius = s;
1110
+ for (let v of this.vertices)
1111
+ v.radius = s;
1112
+ }
1064
1113
 
1065
- class Segment {
1066
-
1067
- constructor(v1, v2, parentMark, id) {
1068
- this.type = "segment";
1069
- this._id = id;
1070
- this.vertex1 = v1;
1071
- this.vertex2 = v2;
1114
+ get vxFillColor(){
1115
+ return this._vxFillColor;
1116
+ }
1072
1117
 
1073
- this.dataScope = undefined;
1074
- this.parent = parentMark;
1118
+ set vxFillColor(s){
1119
+ this._vxFillColor = s;
1120
+ for (let v of this.vertices)
1121
+ v.fillColor = s;
1075
1122
  }
1076
1123
 
1077
- get id() {
1078
- return this.parent.id + "_s_" + this._id;
1124
+ get vxStrokeColor(){
1125
+ return this._vxStrokeColor;
1079
1126
  }
1080
1127
 
1081
- _doTranslate(dx, dy) {
1082
- this.vertex1._doTranslate(dx, dy);
1083
- this.vertex2._doTranslate(dx, dy);
1128
+ set vxStrokeColor(s){
1129
+ this._vxStrokeColor = s;
1130
+ for (let v of this.vertices)
1131
+ v.strokeColor = s;
1084
1132
  }
1085
1133
 
1086
- get x() {
1087
- return (this.vertex1.x + this.vertex2.x)/2;
1134
+ get vxStrokeWidth(){
1135
+ return this._vxStrokeWidth;
1088
1136
  }
1089
1137
 
1090
- get y() {
1091
- return (this.vertex1.y + this.vertex2.y)/2;
1138
+ set vxStrokeWidth(s){
1139
+ this._vxStrokeWidth = s;
1140
+ for (let v of this.vertices)
1141
+ v.strokeWidth = s;
1092
1142
  }
1093
- }
1094
1143
 
1095
- class Path extends Mark {
1096
-
1097
- constructor(args) {
1098
- super(args);
1099
- this.type = "type" in args ? args.type : ItemType.Path;
1144
+ get vxOpacity(){
1145
+ return this._vxOpacity;
1146
+ }
1100
1147
 
1101
- if (!("strokeColor" in this.styles))
1102
- this.styles["strokeColor"] = "#ccc";
1103
- if (!("strokeWidth" in this.styles))
1104
- this.styles["strokeWidth"] = 1;
1105
- if (!("strokeDash" in this.styles))
1106
- this.styles["strokeDash"] = "none";
1148
+ set vxOpacity(s){
1149
+ this._vxOpacity = s;
1150
+ for (let v of this.vertices)
1151
+ v.opacity = s;
1152
+ }
1107
1153
 
1108
- this.vertices = [];
1109
- this.vertexCounter = 0; //for assigning vertex ids
1110
- this.segmentCounter = 0;
1111
- this.segments = [];
1154
+ }
1112
1155
 
1113
- this.anchor = undefined;
1156
+ function evaluatePredicate(itm, p) {
1157
+ if ("field" in p) {
1158
+ if (!itm.dataScope) return false;
1159
+ let f = p["field"];
1160
+ if ("value" in p) {
1161
+ return itm.dataScope.getFieldValue(f) === p["value"];
1162
+ } else if ("interval" in p) {
1163
+ let v = itm.dataScope.getFieldValue(f);
1164
+ return v >= p["interval"][0] && v <= p["interval"][1];
1165
+ } else if ("values" in p) {
1166
+ return p["values"].indexOf(itm.dataScope.getFieldValue(f)) >= 0;
1167
+ } else {
1168
+ return itm.dataScope.hasField(f);
1169
+ }
1170
+ } else if ("channel" in p) {
1171
+ let c = p["channel"];
1172
+ if ("value" in p) {
1173
+ return itm[c] === p["value"];
1174
+ } else if ("interval" in p) {
1175
+ return itm[c] >= p["interval"][0] && itm[c] <= p["interval"][1];
1176
+ } else if ("values" in p) {
1177
+ return p["values"].indexOf(itm[c]) >= 0;
1178
+ }
1179
+ } else if ("type" in p) {
1180
+ return itm.type === p["type"];
1181
+ } else if ("id" in p) {
1182
+ return itm.id === p["id"];
1183
+ } else if ("classId" in p) {
1184
+ return itm.classId === p["classId"];
1185
+ } else if ("fields" in p) {
1186
+ if (!itm.dataScope) return false;
1187
+ let f1 = p["fields"][0], f2 = p["fields"][1],
1188
+ v1 = itm.dataScope.getFieldValue(f1), v2 = itm.dataScope.getFieldValue(f2);
1189
+ switch (p["operator"]) {
1190
+ case "==":
1191
+ return v1 == v2;
1192
+ case ">":
1193
+ return v1 > v2;
1194
+ case ">=":
1195
+ return v1 >= v2;
1196
+ case "<":
1197
+ return v1 < v2;
1198
+ case "<=":
1199
+ return v1 <= v2;
1200
+ }
1201
+ }
1202
+ return false;
1203
+ }
1114
1204
 
1115
- this.closed = false;
1205
+ function findItems(container, predicates) {
1206
+ let result = [];
1207
+ _findItemsRecursive(container, predicates, result);
1208
+ return result;
1209
+ }
1116
1210
 
1117
- this.curveMode = "linear";
1211
+ function _findItemsRecursive(itm, predicates, result) {
1212
+ if (!itm) return;
1213
+ if (itm.type == "axis" || itm.type == "legend" || itm.type == "gridlines") return;
1214
+ if (_matchCriteria(itm, predicates)) {
1215
+ result.push(itm);
1216
+ }
1217
+
1218
+ if (itm.vertices){
1219
+ for (let i of itm.vertices.concat(itm.segments)) {
1220
+ if (_matchCriteria(i, predicates))
1221
+ result.push(i);
1222
+ }
1223
+ } else if (itm.children && itm.children.length > 0) {
1224
+ for (let c of itm.children)
1225
+ _findItemsRecursive(c, predicates, result);
1226
+ }
1227
+ }
1118
1228
 
1119
- //when a path encodes data using its width or height, its geometric bounds may not be the same as its orginal bounds without encoding applied
1120
- this.boundsOffsets = {top: 0, bottom: 0, left: 0, right: 0};
1229
+ function _matchCriteria(cpnt, predicates) {
1230
+ for (let p of predicates) {
1231
+ if (!evaluatePredicate(cpnt, p))
1232
+ return false;
1233
+ }
1234
+ return true;
1235
+ }
1121
1236
 
1122
- this._vxShape = undefined;
1123
- this._vxWidth = 0;
1124
- this._vxHeight = 0;
1125
- this._vxRadius = 0;
1126
- this._vxFillColor = "#555555";
1127
- this._vxStrokeColor = "#aaaaaa";
1128
- this._vxStrokeWidth = 0;
1129
- this._vxOpacity = 1;
1237
+ function getPeers(item, scene) {
1238
+ if (item.type == "vertex") {
1239
+ return _getPeerVertices(item, scene);
1240
+ } else if (item.type == "segment") {
1241
+ return _getPeerSegments(item, scene);
1242
+ } else {
1243
+ // return item.classId ? findItems(scene, d => d.classId == item.classId) : [];
1244
+ return item.classId ? findItems(scene, [{"classId": item.classId}]) : [];
1245
+ }
1246
+ }
1130
1247
 
1131
- if (args !== undefined) {
1132
- for (let vs of Vertex.styles){
1133
- if (vs in args)
1134
- this["_" + vs] = args[vs];
1135
- }
1248
+ //returns an array of peer arrays, peers within each array have the same parent
1249
+ function getPeersGroupedByParent(item, scene) {
1250
+ let result = {}, peers = getPeers(item, scene);
1251
+ for (let p of peers) {
1252
+ let parent = p.parent.id;
1253
+ if (!(parent in result))
1254
+ result[parent] = [];
1255
+ result[parent].push(p);
1256
+ }
1257
+ return Object.keys(result).map(d => result[d]);
1258
+ }
1136
1259
 
1137
- if ("vertices" in args) {
1138
- this._setVertices(args["vertices"]);
1139
- }
1260
+ function _getPeerSegments(segment, container) {
1261
+ if (segment.dataScope) {
1262
+ let parent = segment.parent;
1263
+ if (!parent) throw new Error("segment has no parent mark");
1264
+ let parentPeers = findItems(container, [{"classId": parent.classId}]);
1265
+ let results = [];
1266
+ for (let p of parentPeers) {
1267
+ results = results.concat(p.segments);
1268
+ }
1269
+ return results;
1270
+ } else {
1271
+ let parent = segment.parent;
1272
+ if (!parent) throw new Error("segment has no parent mark");
1273
+ let index = parent.segments.indexOf(segment);
1274
+ let parentPeers = findItems(container, [{"classId": parent.classId}]);
1275
+ let results = [];
1276
+ for (let p of parentPeers) {
1277
+ results.push(p.segments[index]);
1140
1278
  }
1279
+ return results;
1141
1280
  }
1281
+ }
1142
1282
 
1143
- toJSON() {
1144
- let json = super.toJSON();
1145
- json.type = this.type;
1146
- json.id = this.id;
1147
- switch (this.type) {
1148
- case ItemType.Rect:
1149
- json.args.width = this.width;
1150
- json.args.height = this.height;
1151
- json.args.top = this.top;
1152
- json.args.left = this.left;
1153
- break;
1154
- case ItemType.Circle:
1155
- json.args.x = this.x;
1156
- json.args.y = this.y;
1157
- json.args.radius = this.radius;
1158
- break;
1159
- case ItemType.Arc:
1160
- case ItemType.Pie:
1161
- json.args.x = this._x;
1162
- json.args.y = this._y;
1163
- json.args.innerRadius = this._innerRadius;
1164
- json.args.outerRadius = this._outerRadius;
1165
- json.args.startAngle = this._startAngle;
1166
- json.args.endAngle = this._endAngle;
1167
- break;
1168
- default:
1169
- json.vertices = [];
1170
- for (let v of this.vertices)
1171
- json.vertices.push(v.toJSON());
1172
- if (this.type === ItemType.Polygon) {
1173
- json.args.x = this._x;
1174
- json.args.y = this._y;
1175
- json.args.radius = this._radius;
1176
- } else if (this.type === ItemType.Area) {
1177
- json.args.baseline = this._baseline;
1178
- json.args.orientation = this._orientation;
1179
- }
1180
- break;
1181
- }
1182
- // if (this.type === ItemType.Rect) {
1183
- // json.args.width = this.width;
1184
- // json.args.height = this.height;
1185
- // json.args.top = this.top;
1186
- // json.args.left = this.left;
1187
- // } else if (this.type === ItemType.Circle) {
1188
- // json.args.x = this.x;
1189
- // json.args.y = this.y;
1190
- // json.args.radius = this.radius;
1191
- // } else if (this.type === ItemType.Arc) {
1192
- // json.args.x = this._x;
1193
- // json.args.y = this._y;
1194
- // json.args.innerRadius = this._innerRadius;
1195
- // json.args.outerRadius = this._outerRadius;
1196
- // json.args.startAngle = this._startAngle;
1197
- // json.args.endAngle = this._endAngle;
1198
- // } else if (this.type === ItemType.Pie) {
1199
- // json.args.x = this._x;
1200
- // json.args.y = this._y;
1201
- // json.args.radius = this.radius;
1202
- // json.args.startAngle = this.startAngleDeg;
1203
- // json.args.endAngle = this.endAngleDeg;
1204
- // } else {
1205
- // json.vertices = [];
1206
- // for (let v of this.vertices)
1207
- // json.vertices.push(v.toJSON());
1208
- // if (this.type === ItemType.Polygon) {
1209
- // json.args.x = this._x;
1210
- // json.args.y = this._y;
1211
- // json.args.radius = this._radius;
1212
- // } else if (this.type === ItemType.Area) {
1213
- // json.args.baseline = this._baseline;
1214
- // json.args.orientation = this._orientation;
1215
- // }
1216
- // }
1217
- json.vertexCounter = this.vertexCounter;
1218
- json.segmentCounter = this.segmentCounter;
1219
- //do not save segments, anchor and closed for now
1220
- json.curveMode = this.curveMode;
1221
- if (this._bounds)
1222
- json.bounds = this._bounds.toJSON();
1223
- json.boundsOffsets = this.boundsOffsets;
1224
- for (let s of Vertex.styles) {
1225
- json.args[s] = this[s];
1226
- }
1227
- return json;
1228
- }
1229
-
1230
- _setVertices(vertices) {
1231
- let vertex, point;
1232
- this.vertices = [];
1233
- this.segments = [];
1234
- for (let i = 0; i < vertices.length; i++) {
1235
-
1236
- if (i == vertices.length - 1 && vertices[i][0] === vertices[0][0] && vertices[i][1] === vertices[0][1] && this.type === ItemType.Path) {
1237
- continue;
1283
+ function _getPeerVertices(vertex, container) {
1284
+ if (vertex.classId) ; else if (vertex.dataScope) {
1285
+ let parent = vertex.parent;
1286
+ if (!parent) throw new Error("vertex has no parent mark");
1287
+ let parentPeers = findItems(container, [{"classId": parent.classId}]);
1288
+ let results = [];
1289
+ if (parent.type === ItemType.Area) {
1290
+ let idx = parent.vertices.indexOf(vertex), firstHalf = idx < parent.vertices.length/2;
1291
+ for (let p of parentPeers) {
1292
+ let vertices = firstHalf ? p.vertices.slice(0, p.vertices.length/2) : p.vertices.slice(p.vertices.length/2);
1293
+ results = results.concat(vertices.filter(d => d.dataScope));
1238
1294
  }
1239
-
1240
- point = new Point(vertices[i][0], vertices[i][1]);
1241
-
1242
- vertex = new Vertex(point, this, this.vertexCounter++);
1243
-
1244
- for (let vs of Vertex.styles){
1245
- if (this[vs]){
1246
- let temp = vs.replace("vx", "");
1247
- vertex[temp[0].toLowerCase() + temp.slice(1)] = this[vs];
1248
- }
1295
+ } else {
1296
+ for (let p of parentPeers) {
1297
+ results = results.concat(p.vertices.filter(d => d.dataScope));
1249
1298
  }
1250
-
1251
- this.vertices.push(vertex);
1252
- if (i > 0)
1253
- this.segments.push(new Segment(this.vertices[i-1], this.vertices[i], this, this.segmentCounter++));
1254
1299
  }
1255
- //if the first vertex has the same position as the last, this path is closed
1256
- let first = vertices[0], last = vertices[vertices.length - 1];
1257
- if ((first[0] === last[0] && first[1] === last[1]) || this.type === ItemType.Rect) {
1258
- this.closed = true;
1259
- if (!("fillColor" in this.styles))
1260
- this.styles["fillColor"] = "#fff";
1261
- this.segments.push(new Segment(this.vertices[this.vertices.length-1], this.vertices[0], this, this.segmentCounter++));
1300
+ return results;
1301
+ } else {
1302
+ let parent = vertex.parent;
1303
+ if (!parent) throw new Error("vertex has no parent mark");
1304
+ let index = parent.vertices.indexOf(vertex);
1305
+ let parentPeers = findItems(container, [{"classId": parent.classId}]);
1306
+ let results = [];
1307
+ for (let p of parentPeers) {
1308
+ results.push(p.vertices[index]);
1262
1309
  }
1310
+ return results;
1263
1311
  }
1312
+ }
1264
1313
 
1265
- copyPropertiesTo(target) {
1266
- target.attrs = Object.assign({}, this.attrs);
1267
- target.styles = Object.assign({}, this.styles);
1268
- for (let vs of Vertex.styles){
1269
- if (this["_"+vs])
1270
- target["_"+vs] = this["_"+vs];
1271
- }
1272
- if (this._dataScope)
1273
- target._dataScope = this._dataScope.clone();
1274
- target.closed = this.closed;
1275
- target.curveMode = this.curveMode;
1276
- target.vertices = [];
1277
- target.segments = [];
1278
- for (let v of this.vertices) {
1279
- target.vertices.push(v._clone(target));
1280
- }
1281
- target.segmentCounter = 0;
1282
- for (let i = 1; i < target.vertices.length; i++) {
1283
- target.segments.push(new Segment(target.vertices[i-1], target.vertices[i], target, target.segmentCounter++));
1314
+ function getClosestLayout(item, type) {
1315
+ let parent = item.parent;
1316
+ while (parent && parent.type != ItemType.Scene) {
1317
+ if (parent.layout) {
1318
+ if ( (!type) || (type && parent.layout.type === type))
1319
+ return parent.layout;
1284
1320
  }
1285
- if (target.closed)
1286
- target.segments.push(new Segment(target.vertices[target.vertices.length-1], target.vertices[0], target, target.segmentCounter++));
1321
+ parent = parent.parent;
1287
1322
  }
1323
+ return undefined;
1324
+ }
1288
1325
 
1289
- /*
1290
- * returns the bounds without incorporating transformations involving rotation
1291
- */
1292
- get bounds() {
1293
- if (!this._bounds)
1294
- this._updateBounds();
1295
- return this._bounds;
1326
+ function getCellBoundsInLayout(item) {
1327
+ let itm = item, parent = item.parent;
1328
+ while (parent && parent.type != ItemType.Scene) {
1329
+ if (parent.layout){
1330
+ let idx = parent.children.findIndex(d => d == itm);
1331
+ return parent.layout.cellBounds[idx];
1332
+ }
1333
+ itm = itm.parent;
1334
+ parent = itm.parent;
1296
1335
  }
1336
+ return undefined;
1337
+ }
1297
1338
 
1298
- /**
1299
- * return the bounds as if the path does not encode data
1300
- */
1301
- get refBounds() {
1302
- if (!this._bounds)
1303
- this._updateBounds();
1304
- let ht = (this._bounds.bottom + this.boundsOffsets.bottom) - (this._bounds.top - this.boundsOffsets.top),
1305
- wd = this._bounds.right + this.boundsOffsets.right - (this._bounds.left - this.boundsOffsets.left);
1306
- return new Rectangle(this._bounds.left - this.boundsOffsets.left, this._bounds.top - this.boundsOffsets.top, wd, ht);
1339
+ function getCellIndexInLayout(item) {
1340
+ let itm = item, parent = item.parent;
1341
+ while (parent && parent.type != ItemType.Scene) {
1342
+ if (parent.layout){
1343
+ return parent.children.findIndex(d => d == itm);
1344
+ }
1345
+ itm = itm.parent;
1346
+ parent = itm.parent;
1307
1347
  }
1348
+ return undefined;
1349
+ }
1308
1350
 
1309
- get x() {
1310
- return this.bounds.x;
1351
+ function getCellBoundsInGridLayout(item) {
1352
+ let itm = item, parent = item.parent;
1353
+ while (parent && parent.type != ItemType.Scene) {
1354
+ if (parent.layout && parent.layout.type == LayoutType.Grid){
1355
+ let idx = parent.children.findIndex(d => d == itm);
1356
+ return parent.layout.cellBounds[idx];
1357
+ }
1358
+ itm = itm.parent;
1359
+ parent = itm.parent;
1311
1360
  }
1361
+ return undefined;
1362
+ }
1312
1363
 
1313
- get y() {
1314
- return this.bounds.y;
1364
+ function getTopLevelLayout(item, type) {
1365
+ let parent = item.parent, layout = undefined;
1366
+ while (parent && parent.type !== ItemType.Scene) {
1367
+ if (parent.layout)
1368
+ if ( (!type) || (type && parent.layout.type === type))
1369
+ layout = parent.layout;
1370
+ parent = parent.parent;
1315
1371
  }
1372
+ return layout;
1373
+ }
1316
1374
 
1317
- get strokeColor() {
1318
- return this.styles["strokeColor"];
1375
+ function getEncodingKey(item) {
1376
+ if (item.classId) {
1377
+ return item.classId;
1378
+ } else if (item.type == "vertex" && item.dataScope) { //vertex created from densify
1379
+ if (item.parent.type === ItemType.Area) {
1380
+ let firstHalf = item.parent.vertices.indexOf(item) < item.parent.vertices.length/2;
1381
+ return item.parent.classId + "_v_" + (firstHalf ? 0 : item.parent.vertices.length-1) ;
1382
+ }
1383
+ else
1384
+ return item.parent.classId + "_v";
1385
+ } else if (item.type == "vertex") { //vertex with index
1386
+ return item.parent.classId + "_v_" + item.parent.vertices.indexOf(item);
1387
+ } else if (item.type == "segment" && item.dataScope) { //segment created from densify
1388
+ return item.parent.classId + "_s";
1389
+ } else if (item.type == "segment") { //segment with index
1390
+ return item.parent.classId + "_s_" + item.parent.segments.indexOf(item);
1391
+ } else {
1392
+ return null;
1319
1393
  }
1394
+ }
1320
1395
 
1321
- set strokeColor(c) {
1322
- this.styles["strokeColor"] = c;
1323
- }
1396
+ function sameClass(item1, item2) {
1397
+ return getEncodingKey(item1).split("_")[0] === getEncodingKey(item2).split("_")[0];
1398
+ }
1324
1399
 
1325
- get strokeWidth() {
1326
- return this.styles["strokeWidth"];
1400
+ function getParents(items) {
1401
+ let result = [];
1402
+ for (let p of items) {
1403
+ if (p.parent && result.indexOf(p.parent) < 0)
1404
+ result.push(p.parent);
1327
1405
  }
1406
+ return result;
1407
+ }
1328
1408
 
1329
- set strokeWidth(c) {
1330
- this.styles["strokeWidth"] = c;
1409
+ function getAllChildren(cpnt) {
1410
+ let result = [];
1411
+ if (cpnt.children && cpnt.children.length > 0) {
1412
+ for (let c of cpnt.children) {
1413
+ result.push(c);
1414
+ result = result.concat(getAllChildren(c));
1415
+ }
1331
1416
  }
1417
+ return result;
1418
+ }
1332
1419
 
1333
- get fillColor() {
1334
- return this.styles["fillColor"];
1335
- }
1420
+ function getLeafMarks(cpnt) {
1421
+ let result = [];
1422
+ if (isMark(cpnt)) {
1423
+ result.push(cpnt);
1424
+ } else if (cpnt.children && cpnt.children.length > 0 && !isGuide(cpnt)) {
1425
+ for (let c of cpnt.children) {
1426
+ result = result.concat(getLeafMarks(c));
1427
+ }
1428
+ }
1429
+ return result;
1430
+ }
1336
1431
 
1337
- set fillColor(c) {
1338
- this.styles["fillColor"] = c;
1432
+ function getLeafItems(cpnt) {
1433
+ let result = [];
1434
+ if (cpnt.children && cpnt.children.length > 0) {
1435
+ for (let c of cpnt.children) {
1436
+ result = result.concat(getLeafItems(c));
1437
+ }
1438
+ } else {
1439
+ result.push(cpnt);
1339
1440
  }
1441
+ return result;
1442
+ }
1340
1443
 
1341
- get strokeDash() {
1342
- return this.styles["strokeDash"];
1343
- }
1444
+ function isGuide(item) {
1445
+ return item.type === ItemType.Axis || item.type === ItemType.Legend || item.type === ItemType.Gridlines;
1446
+ }
1344
1447
 
1345
- set strokeDash(c) {
1346
- this.styles["strokeDash"] = c;
1347
- }
1448
+ function isMark(cmpnt) {
1449
+ return cmpnt instanceof Mark;
1450
+ }
1348
1451
 
1349
- _doTranslate(dx, dy) {
1350
- for (let v of this.vertices) {
1351
- v._doTranslate(dx, dy);
1452
+ function isPath(cmpnt) {
1453
+ return cmpnt instanceof Path;
1454
+ }
1455
+
1456
+ const ItemCounter = {
1457
+ "area" : 0,
1458
+ "rect" : 0,
1459
+ "circle": 0,
1460
+ "pie": 0,
1461
+ "line": 0,
1462
+ "path" : 0,
1463
+ "ring" : 0,
1464
+ "arc": 0,
1465
+ "image": 0,
1466
+ "pointText": 0,
1467
+ "collection": 0,
1468
+ "group": 0,
1469
+ "scene": 0,
1470
+ "axis": 0,
1471
+ "glyph": 0,
1472
+ "legend": 0,
1473
+ "polygon": 0,
1474
+ "gridlines": 0,
1475
+ "LinearGradient": 0,
1476
+ "link": 0,
1477
+ "scale": 0,
1478
+ "datatable": 0
1479
+ };
1480
+
1481
+ function canAlign(items, direction, scene){
1482
+ if (direction == Alignment.Top || direction == Alignment.Bottom || direction == Alignment.Middle) {
1483
+ for (let item of items) {
1484
+ if (!canMoveVertically(item, scene))
1485
+ return false;
1352
1486
  }
1353
- this._updateBounds();
1487
+ return true;
1354
1488
  }
1355
1489
 
1356
- //by default, with respect to the center of bounds
1357
- resize(wd, ht, xRef, yRef) {
1358
- let bounds = this.bounds, bWidth = bounds.width === 0 ? 1 : bounds.width, bHeight = bounds.height === 0 ? 1 : bounds.height;
1359
- if (xRef === "right") {
1360
- for (let v of this.vertices) {
1361
- v.x = bounds.right - (wd/bWidth) * (bounds.right - v.x);
1362
- }
1363
- } else {
1364
- for (let v of this.vertices) {
1365
- v.x = bounds.left + (wd/bWidth) * (v.x - bounds.left);
1366
- }
1367
- }
1368
- if (yRef === "top") {
1369
- for (let v of this.vertices) {
1370
- v.y = bounds.top + (ht/bHeight) * (v.y - bounds.top);
1371
- }
1372
- } else {
1373
- for (let v of this.vertices) {
1374
- v.y = bounds.bottom - (ht/bHeight) * (bounds.bottom - v.y);
1375
- }
1490
+ if (direction == Alignment.Left || direction == Alignment.Right || direction == Alignment.Center) {
1491
+ for (let item of items) {
1492
+ if (!canMoveHorizontally(item, scene))
1493
+ return false;
1376
1494
  }
1377
- this._updateBounds();
1495
+ return true;
1378
1496
  }
1497
+ }
1379
1498
 
1380
- _updateBounds() {
1381
- let vx = this.vertices.map(d => d.x),
1382
- vy = this.vertices.map(d => d.y);
1383
-
1384
- let left = Math.min(...vx), top = Math.min(...vy), right = Math.max(...vx), btm = Math.max(...vy);
1385
-
1386
- this._bounds = new Rectangle(left, top, right - left, btm - top);
1387
- if (this.type === ItemType.Line || this.type === ItemType.Path) {
1388
- let sw = this.styles["strokeWidth"] ? this.styles["strokeWidth"] : 1;
1389
- if (left === right)
1390
- this._bounds = new Rectangle(left - sw/2, top, right - left + sw, btm - top);
1391
- else if (top === btm)
1392
- this._bounds = new Rectangle(left, top - sw/2, right - left, btm - top + sw);
1499
+ function canMoveHorizontally(item, scene) {
1500
+ if (scene.getEncodingByItem(item, "x"))
1501
+ return false;
1502
+ if (item.parent && item.parent.layout) {
1503
+ let layout = item.parent.layout;
1504
+ if (layout.type == LayoutType.Grid && layout.numCols > 1) {
1505
+ return false;
1393
1506
  }
1394
1507
  }
1395
-
1396
- addVertex(x, y, i) {
1397
- let vertex = new Vertex(new Point(x, y), this, this.vertexCounter++);
1398
- this.vertices.splice(i, 0, vertex);
1399
- //TODO: handle segments
1508
+ if (item.parent && item.parent.type != ItemType.Scene){
1509
+ return canMoveHorizontally(item.parent, scene);
1400
1510
  }
1511
+ return true;
1512
+ }
1401
1513
 
1402
- sortVertices(channel, descending) {
1403
- this.vertices.sort((a,b) => a[channel] - b[channel]);
1404
- if (descending)
1405
- this.vertices.reverse();
1406
- for (let i = 0; i < this.segments.length; i++) {
1407
- let segment = this.segments[i];
1408
- segment.vertex1 = this.vertices[i];
1409
- segment.vertex2 = this.vertices[(i+1)%this.vertices.length];
1514
+ function canMoveVertically(item, scene) {
1515
+ if (scene.getEncodingByItem(item, "y"))
1516
+ return false;
1517
+ if (item.parent && item.parent.layout) {
1518
+ let layout = item.parent.layout;
1519
+ if (layout.type == LayoutType.Grid && layout.numRows > 1) {
1520
+ return false;
1410
1521
  }
1411
1522
  }
1412
-
1413
- sortVerticesByData(field, descending, order) {
1414
- let f;
1415
- if (order)
1416
- f = (a, b) => order.indexOf(a.dataScope.getFieldValue(field)) - order.indexOf(b.dataScope.getFieldValue(field));
1417
- else
1418
- f = (a, b) => (a.dataScope.getFieldValue(field) < b.dataScope.getFieldValue(field) ? -1 : 1 );
1419
- this.vertices.sort(f);
1420
- if (descending)
1421
- this.vertices.reverse();
1422
- for (let i = 0; i < this.segments.length; i++) {
1423
- let segment = this.segments[i];
1424
- segment.vertex1 = this.vertices[i];
1425
- segment.vertex2 = this.vertices[(i+1)%this.vertices.length];
1426
- }
1523
+ if (item.parent && item.parent.type != ItemType.Scene){
1524
+ return canMoveVertically(item.parent, scene);
1427
1525
  }
1526
+ return true;
1527
+ }
1428
1528
 
1429
- getSVGPathData() {
1430
- let p = d3__namespace.path();
1431
- let curve = this._getD3CurveFunction(this.curveMode)(p);
1432
- curve.lineStart();
1433
- for (let vertex of this.vertices) {
1434
- curve.point(vertex.x, vertex.y);
1435
- }
1436
- if (this.closed)
1437
- curve.point(this.vertices[0].x, this.vertices[0].y);
1438
- curve.lineEnd();
1439
-
1440
- return p._;
1441
- }
1442
-
1443
- // toSVG() {
1529
+ var CanvasProvider = {
1530
+ canvas : undefined,
1444
1531
 
1445
- // }
1532
+ getCanvas: function() {
1533
+ if (!window)
1534
+ return null;
1535
+ if (this.canvas === undefined) {
1536
+ this.canvas = document.createElement('canvas');
1537
+ }
1538
+ return this.canvas;
1539
+ },
1446
1540
 
1447
- // fromSVG() {
1541
+ getContext: function() {
1542
+ var canvas = this.getCanvas();
1543
+ return canvas ? canvas.getContext('2d') : null;
1544
+ },
1545
+ };
1448
1546
 
1449
- // }
1547
+ var SVGProvider = {
1548
+ svg: undefined,
1450
1549
 
1451
- get firstVertex() {
1452
- return this.vertices[0];
1550
+ getSVG: function() {
1551
+ if (!window)
1552
+ return null;
1553
+ if (this.svg === undefined) {
1554
+ this.svg = document.createElement('svg');
1555
+ }
1556
+ return this.svg;
1453
1557
  }
1558
+ };
1454
1559
 
1455
- get firstSegment() {
1456
- return this.segments[0];
1457
- }
1560
+ // export function getTextWidth(text, font) {
1561
+ // let context = CanvasProvider.getContext();
1562
+ // context.font = font;
1563
+ // let metrics = context.measureText(text);
1564
+ // return metrics.width;
1565
+ // }
1458
1566
 
1459
- get lastVertex() {
1460
- return this.vertices[this.vertices.length - 1];
1461
- }
1567
+ function getTextSize(text, font, fontSize) {
1568
+ let context = CanvasProvider.getContext();
1569
+ context.font = font;
1570
+ let metrics = context.measureText(text);
1571
+ if (metrics.fontBoundingBoxAscent)
1572
+ return {width: metrics.width, height: metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent};
1573
+ else if (metrics.actualBoundingBoxAscent)
1574
+ return {width: metrics.width, height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent};
1575
+ else
1576
+ return {width: metrics.width, height: fontSize};
1577
+ }
1462
1578
 
1463
- get lastSegment() {
1464
- return this.segments[this.segments.length - 1];
1579
+ function getTopLevelGroup(item) {
1580
+ let parent = item.parent;
1581
+ if (parent.type == ItemType.Scene)
1582
+ return item;
1583
+ else
1584
+ return getTopLevelGroup(parent);
1585
+ }
1586
+
1587
+ function getTopLevelCollection(item) {
1588
+ let parent = item.parent;
1589
+ if (item.type == ItemType.Collection) {
1590
+ if (parent.type == ItemType.Collection) {
1591
+ return getTopLevelCollection(parent);
1592
+ } else
1593
+ return item;
1594
+ } else if (parent.type != ItemType.Scene) {
1595
+ return getTopLevelCollection(parent);
1596
+ } else {
1597
+ return undefined;
1465
1598
  }
1599
+ }
1466
1600
 
1467
- _getD3CurveFunction(v){
1468
- switch(v) {
1469
- case CurveMode.Natural:
1470
- return d3__namespace.curveNatural;
1471
- case CurveMode.Basis:
1472
- return d3__namespace.curveBasis;
1473
- case CurveMode.BumpX:
1474
- return d3__namespace.curveBumpX;
1475
- case CurveMode.BumpY:
1476
- return d3__namespace.curveBumpY;
1477
- case CurveMode.Linear:
1478
- return d3__namespace.curveLinear;
1479
- case CurveMode.Step:
1480
- return d3__namespace.curveStep;
1481
- case CurveMode.CatmullRom:
1482
- return d3__namespace.curveCatmullRom;
1483
- case CurveMode.Cardinal:
1484
- return d3__namespace.curveCardinal;
1485
- default:
1486
- return d3__namespace.curveLinear;
1487
- }
1488
- }
1489
-
1490
- get vxShape(){
1491
- return this._vxShape;
1492
- }
1493
-
1494
- set vxShape(s){
1495
- this._vxShape = s;
1496
- for (let v of this.vertices)
1497
- v.shape = s;
1498
- }
1601
+ function polar2Cartesian(cx, cy, r, deg){
1602
+ let x = r * Math.cos(degree2radian(deg)),
1603
+ y = r * Math.sin(degree2radian(deg));
1604
+ return [x + cx, cy - y];
1605
+ }
1499
1606
 
1500
- get vxWidth(){
1501
- return this._vxWidth;
1502
- }
1607
+ function cartesian2Polar(x, y, cx, cy){
1608
+ let d = radian2degree(Math.atan2(cy - y, x - cx));
1609
+ d = Math.round( d * 10 + Number.EPSILON ) / 10;
1610
+ if (d < 0) d += 360;
1611
+ let r = Math.sqrt(Math.pow(x-cx, 2) + Math.pow(y-cy, 2));
1612
+ r = Math.round( r * 10 + Number.EPSILON ) / 10;
1613
+ return [d, r];
1614
+ }
1503
1615
 
1504
- set vxWidth(s){
1505
- this._vxWidth = s;
1506
- for (let v of this.vertices)
1507
- v.width = s;
1508
- }
1616
+ function degree2radian(d){
1617
+ return d * Math.PI/180;
1618
+ }
1509
1619
 
1510
- get vxHeight(){
1511
- return this._vxHeight;
1512
- }
1620
+ function radian2degree(r){
1621
+ return r * 180 / Math.PI;
1622
+ }
1513
1623
 
1514
- set vxHeight(s){
1515
- this._vxHeight = s;
1516
- for (let v of this.vertices)
1517
- v.height = s;
1624
+ function CheckAreaOrien(area){
1625
+ let VNum = area.vertices.length;
1626
+ for (let i = 0; i < area.vertices.length / 2; i++) {
1627
+ let Vid1 = i, Vid2 = VNum - i - 1;
1628
+ let peer1 = area.vertices[Vid1], peer2 = area.vertices[Vid2];
1629
+ if (peer1.x == peer2.x && peer1.y == peer2.y) {
1630
+ continue;
1631
+ } else {
1632
+ if (peer1.x == peer2.x) {
1633
+ return "horizontal";
1634
+ } else {
1635
+ return "vertical";
1636
+ }
1637
+ }
1518
1638
  }
1639
+ }
1519
1640
 
1520
- get vxRadius(){
1521
- return this._vxRadius;
1522
- }
1641
+ class Layout {
1523
1642
 
1524
- set vxRadius(s){
1525
- this._vxRadius = s;
1526
- for (let v of this.vertices)
1527
- v.radius = s;
1528
- }
1643
+ constructor(args){
1644
+ this.group = undefined;
1645
+ }
1529
1646
 
1530
- get vxFillColor(){
1531
- return this._vxFillColor;
1532
- }
1647
+ run(){}
1533
1648
 
1534
- set vxFillColor(s){
1535
- this._vxFillColor = s;
1536
- for (let v of this.vertices)
1537
- v.fillColor = s;
1538
- }
1649
+ clone(){}
1650
+ }
1539
1651
 
1540
- get vxStrokeColor(){
1541
- return this._vxStrokeColor;
1542
- }
1652
+ class GridLayout extends Layout {
1543
1653
 
1544
- set vxStrokeColor(s){
1545
- this._vxStrokeColor = s;
1546
- for (let v of this.vertices)
1547
- v.strokeColor = s;
1654
+ constructor(args) {
1655
+ super();
1656
+ this.type = "grid";
1657
+ this._numCols = args["numCols"];
1658
+ this._numRows = args["numRows"];
1659
+ this._dir = ("dir" in args) ? args["dir"] : [GridLayout.direction.Left2Right, GridLayout.direction.Top2Bottom];
1660
+ // this._hDir = ("hDir" in args) ? args["hDir"] : GridLayout.direction.Left2Right;
1661
+ // this._vDir = ("vDir" in args) ? args["vDir"] : GridLayout.direction.Top2Bottom;
1662
+ this._rowGap = "rowGap" in args && args["rowGap"] !== undefined ? args["rowGap"] : 5;
1663
+ this._colGap = "colGap" in args && args["colGap"] !== undefined ? args["colGap"] : 5;
1664
+ this._cellHorzAlignment = "horzCellAlignment" in args && this._validateCellAlignment("h", args["horzCellAlignment"]) ? args["horzCellAlignment"] : Alignment.Left;
1665
+ this._cellVertAlignment = "vertCellAlignment" in args && this._validateCellAlignment("v", args["vertCellAlignment"]) ? args["vertCellAlignment"] : Alignment.Bottom;
1666
+ if (!this._numCols && !this._numRows)
1667
+ this._numRows = 1;
1548
1668
  }
1549
1669
 
1550
- get vxStrokeWidth(){
1551
- return this._vxStrokeWidth;
1552
- }
1670
+ _validateCellAlignment(orientation, v) {
1671
+ if (orientation === "h" && [Alignment.Left, Alignment.Center, Alignment.Right].indexOf(v) >= 0){
1672
+ return true;
1673
+ } else if (orientation === "v" && [Alignment.Top, Alignment.Middle, Alignment.Bottom].indexOf(v) >= 0){
1674
+ return true;
1675
+ }
1676
+ console.warn("Invalid alignment:", v);
1677
+ return false;
1553
1678
 
1554
- set vxStrokeWidth(s){
1555
- this._vxStrokeWidth = s;
1556
- for (let v of this.vertices)
1557
- v.strokeWidth = s;
1558
1679
  }
1559
1680
 
1560
- get vxOpacity(){
1561
- return this._vxOpacity;
1681
+ toJSON() {
1682
+ let json = {args: {}};
1683
+ json.type = this.type;
1684
+ json.args.numCols = this._numCols;
1685
+ json.args.numRows = this._numRows;
1686
+ json.args.colGap = this._colGap;
1687
+ json.args.rowGap = this._rowGap;
1688
+ json.args.horzCellAlignment = this._cellHorzAlignment;
1689
+ json.args.vertCellAlignment = this._cellVertAlignment;
1690
+ json.left = this._left;
1691
+ json.top = this._top;
1692
+ json.args.dir = this._dir;
1693
+ return json;
1562
1694
  }
1563
1695
 
1564
- set vxOpacity(s){
1565
- this._vxOpacity = s;
1566
- for (let v of this.vertices)
1567
- v.opacity = s;
1696
+ clone() {
1697
+ return new GridLayout({
1698
+ numCols: this._numCols,
1699
+ numRows: this._numRows,
1700
+ // hDir: this._hDir,
1701
+ // vDir: this._vDir,
1702
+ dir: this._dir,
1703
+ colGap: this._colGap,
1704
+ rowGap: this._rowGap
1705
+ });
1568
1706
  }
1569
1707
 
1570
- }
1571
-
1572
- function evaluatePredicate(itm, p) {
1573
- if ("field" in p) {
1574
- if (!itm.dataScope) return false;
1575
- let f = p["field"];
1576
- if ("value" in p) {
1577
- return itm.dataScope.getFieldValue(f) === p["value"];
1578
- } else if ("interval" in p) {
1579
- let v = itm.dataScope.getFieldValue(f);
1580
- return v >= p["interval"][0] && v <= p["interval"][1];
1581
- } else if ("values" in p) {
1582
- return p["values"].indexOf(itm.dataScope.getFieldValue(f)) >= 0;
1583
- } else {
1584
- return itm.dataScope.hasField(f);
1585
- }
1586
- } else if ("channel" in p) {
1587
- let c = p["channel"];
1588
- if ("value" in p) {
1589
- return itm[c] === p["value"];
1590
- } else if ("interval" in p) {
1591
- return itm[c] >= p["interval"][0] && itm[c] <= p["interval"][1];
1592
- } else if ("values" in p) {
1593
- return p["values"].indexOf(itm[c]) >= 0;
1594
- }
1595
- } else if ("type" in p) {
1596
- return itm.type === p["type"];
1597
- } else if ("id" in p) {
1598
- return itm.id === p["id"];
1599
- } else if ("classId" in p) {
1600
- return itm.classId === p["classId"];
1601
- } else if ("fields" in p) {
1602
- if (!itm.dataScope) return false;
1603
- let f1 = p["fields"][0], f2 = p["fields"][1],
1604
- v1 = itm.dataScope.getFieldValue(f1), v2 = itm.dataScope.getFieldValue(f2);
1605
- switch (p["operator"]) {
1606
- case "==":
1607
- return v1 == v2;
1608
- case ">":
1609
- return v1 > v2;
1610
- case ">=":
1611
- return v1 >= v2;
1612
- case "<":
1613
- return v1 < v2;
1614
- case "<=":
1615
- return v1 <= v2;
1708
+ get cellBounds() {
1709
+ let numCols, numRows, group = this.group, colGap = this._colGap, rowGap = this._rowGap;
1710
+ if (this._numRows) {
1711
+ numRows = this._numRows;
1712
+ numCols = Math.ceil(this.group.children.length/this._numRows);
1713
+ } else if (this._numCols) {
1714
+ numCols = this._numCols;
1715
+ numRows = Math.ceil(this.group.children.length/this._numCols);
1616
1716
  }
1617
- }
1618
- return false;
1619
- }
1620
-
1621
- function findItems(container, predicates) {
1622
- let result = [];
1623
- _findItemsRecursive(container, predicates, result);
1624
- return result;
1625
- }
1626
1717
 
1627
- function _findItemsRecursive(itm, predicates, result) {
1628
- if (!itm) return;
1629
- if (itm.type == "axis" || itm.type == "legend" || itm.type == "gridlines") return;
1630
- if (_matchCriteria(itm, predicates)) {
1631
- result.push(itm);
1632
- }
1633
-
1634
- if (itm.vertices){
1635
- for (let i of itm.vertices.concat(itm.segments)) {
1636
- if (_matchCriteria(i, predicates))
1637
- result.push(i);
1718
+ let bounds = group.children.map(d => d.bounds);
1719
+ if (this._left === undefined) {
1720
+ let lefts = bounds.map(d => d.left),
1721
+ tops = bounds.map(d => d.top);
1722
+ this._left = Math.min(...lefts);
1723
+ this._top = Math.min(...tops);
1638
1724
  }
1639
- } else if (itm.children && itm.children.length > 0) {
1640
- for (let c of itm.children)
1641
- _findItemsRecursive(c, predicates, result);
1642
- }
1643
- }
1644
1725
 
1645
- function _matchCriteria(cpnt, predicates) {
1646
- for (let p of predicates) {
1647
- if (!evaluatePredicate(cpnt, p))
1648
- return false;
1649
- }
1650
- return true;
1651
- }
1652
-
1653
- function getPeers(item, scene) {
1654
- if (item.type == "vertex") {
1655
- return _getPeerVertices(item, scene);
1656
- } else if (item.type == "segment") {
1657
- return _getPeerSegments(item, scene);
1658
- } else {
1659
- // return item.classId ? findItems(scene, d => d.classId == item.classId) : [];
1660
- return item.classId ? findItems(scene, [{"classId": item.classId}]) : [];
1661
- }
1662
- }
1663
-
1664
- //returns an array of peer arrays, peers within each array have the same parent
1665
- function getPeersGroupedByParent(item, scene) {
1666
- let result = {}, peers = getPeers(item, scene);
1667
- for (let p of peers) {
1668
- let parent = p.parent.id;
1669
- if (!(parent in result))
1670
- result[parent] = [];
1671
- result[parent].push(p);
1672
- }
1673
- return Object.keys(result).map(d => result[d]);
1674
- }
1675
-
1676
- function _getPeerSegments(segment, container) {
1677
- if (segment.dataScope) {
1678
- let parent = segment.parent;
1679
- if (!parent) throw new Error("segment has no parent mark");
1680
- let parentPeers = findItems(container, [{"classId": parent.classId}]);
1681
- let results = [];
1682
- for (let p of parentPeers) {
1683
- results = results.concat(p.segments);
1684
- }
1685
- return results;
1686
- } else {
1687
- let parent = segment.parent;
1688
- if (!parent) throw new Error("segment has no parent mark");
1689
- let index = parent.segments.indexOf(segment);
1690
- let parentPeers = findItems(container, [{"classId": parent.classId}]);
1691
- let results = [];
1692
- for (let p of parentPeers) {
1693
- results.push(p.segments[index]);
1694
- }
1695
- return results;
1696
- }
1697
- }
1698
-
1699
- function _getPeerVertices(vertex, container) {
1700
- if (vertex.classId) ; else if (vertex.dataScope) {
1701
- let parent = vertex.parent;
1702
- if (!parent) throw new Error("vertex has no parent mark");
1703
- let parentPeers = findItems(container, [{"classId": parent.classId}]);
1704
- let results = [];
1705
- if (parent.type === ItemType.Area) {
1706
- let idx = parent.vertices.indexOf(vertex), firstHalf = idx < parent.vertices.length/2;
1707
- for (let p of parentPeers) {
1708
- let vertices = firstHalf ? p.vertices.slice(0, p.vertices.length/2) : p.vertices.slice(p.vertices.length/2);
1709
- results = results.concat(vertices.filter(d => d.dataScope));
1710
- }
1711
- } else {
1712
- for (let p of parentPeers) {
1713
- results = results.concat(p.vertices.filter(d => d.dataScope));
1726
+ let wds = bounds.map(d => d.width),
1727
+ hts = bounds.map(d => d.height),
1728
+ cellWidth = Math.max(...wds),
1729
+ cellHeight = Math.max(...hts);
1730
+
1731
+ //cell size should be determined by the scale range extent if bound to data
1732
+ let leftOffset = 0; //, topOffset = 0;
1733
+
1734
+ let xEncs = group.getInternalEncodings("x"),
1735
+ yEncs = group.getInternalEncodings("y"),
1736
+ wdEncs = group.getInternalEncodings("width"),
1737
+ htEncs = group.getInternalEncodings("height");
1738
+ if (xEncs.length > 0) {
1739
+ let xEnc = xEncs[xEncs.length -1];
1740
+ cellWidth = xEnc.scale.rangeExtent;
1741
+ leftOffset = xEnc.scale.range[0];
1742
+ if (xEnc.scale.type === "point") {
1743
+ //TODO: need to handle variable sizes
1744
+ cellWidth += xEnc.anyItem.bounds.width;
1714
1745
  }
1746
+ } else if (wdEncs.length > 0 && wdEncs[wdEncs.length -1]._rectNegativeValues) { //width encoding with negative values
1747
+ cellWidth = wdEncs[wdEncs.length -1].scale.rangeExtent;
1748
+ leftOffset = wdEncs[wdEncs.length -1].scale.range[0];
1715
1749
  }
1716
- return results;
1717
- } else {
1718
- let parent = vertex.parent;
1719
- if (!parent) throw new Error("vertex has no parent mark");
1720
- let index = parent.vertices.indexOf(vertex);
1721
- let parentPeers = findItems(container, [{"classId": parent.classId}]);
1722
- let results = [];
1723
- for (let p of parentPeers) {
1724
- results.push(p.vertices[index]);
1725
- }
1726
- return results;
1727
- }
1728
- }
1729
-
1730
- function getClosestLayout(item, type) {
1731
- let parent = item.parent;
1732
- while (parent && parent.type != ItemType.Scene) {
1733
- if (parent.layout) {
1734
- if ( (!type) || (type && parent.layout.type === type))
1735
- return parent.layout;
1736
- }
1737
- parent = parent.parent;
1738
- }
1739
- return undefined;
1740
- }
1741
-
1742
- function getCellBoundsInLayout(item) {
1743
- let itm = item, parent = item.parent;
1744
- while (parent && parent.type != ItemType.Scene) {
1745
- if (parent.layout){
1746
- let idx = parent.children.findIndex(d => d == itm);
1747
- return parent.layout.cellBounds[idx];
1748
- }
1749
- itm = itm.parent;
1750
- parent = itm.parent;
1751
- }
1752
- return undefined;
1753
- }
1754
-
1755
- function getCellIndexInLayout(item) {
1756
- let itm = item, parent = item.parent;
1757
- while (parent && parent.type != ItemType.Scene) {
1758
- if (parent.layout){
1759
- return parent.children.findIndex(d => d == itm);
1760
- }
1761
- itm = itm.parent;
1762
- parent = itm.parent;
1763
- }
1764
- return undefined;
1765
- }
1766
-
1767
- function getCellBoundsInGridLayout(item) {
1768
- let itm = item, parent = item.parent;
1769
- while (parent && parent.type != ItemType.Scene) {
1770
- if (parent.layout && parent.layout.type == LayoutType.Grid){
1771
- let idx = parent.children.findIndex(d => d == itm);
1772
- return parent.layout.cellBounds[idx];
1750
+ if (yEncs.length > 0) {
1751
+ let yEnc = yEncs[yEncs.length -1];
1752
+ cellHeight = yEnc.scale.rangeExtent;
1753
+ if (yEnc.scale.type === "point") {
1754
+ //TODO: need to handle variable sizes
1755
+ cellHeight += yEnc.anyItem.bounds.height;
1756
+ }
1757
+ } else if (htEncs.length > 0 && htEncs[htEncs.length -1]._rectNegativeValues) { //width encoding with negative values
1758
+ cellHeight = htEncs[htEncs.length -1].scale.rangeExtent;
1773
1759
  }
1774
- itm = itm.parent;
1775
- parent = itm.parent;
1776
- }
1777
- return undefined;
1778
- }
1779
-
1780
- function getTopLevelLayout(item, type) {
1781
- let parent = item.parent, layout = undefined;
1782
- while (parent && parent.type !== ItemType.Scene) {
1783
- if (parent.layout)
1784
- if ( (!type) || (type && parent.layout.type === type))
1785
- layout = parent.layout;
1786
- parent = parent.parent;
1787
- }
1788
- return layout;
1789
- }
1790
-
1791
- function getEncodingKey(item) {
1792
- if (item.classId) {
1793
- return item.classId;
1794
- } else if (item.type == "vertex" && item.dataScope) { //vertex created from densify
1795
- if (item.parent.type === ItemType.Area) {
1796
- let firstHalf = item.parent.vertices.indexOf(item) < item.parent.vertices.length/2;
1797
- return item.parent.classId + "_v_" + (firstHalf ? 0 : item.parent.vertices.length-1) ;
1798
- }
1799
- else
1800
- return item.parent.classId + "_v";
1801
- } else if (item.type == "vertex") { //vertex with index
1802
- return item.parent.classId + "_v_" + item.parent.vertices.indexOf(item);
1803
- } else if (item.type == "segment" && item.dataScope) { //segment created from densify
1804
- return item.parent.classId + "_s";
1805
- } else if (item.type == "segment") { //segment with index
1806
- return item.parent.classId + "_s_" + item.parent.segments.indexOf(item);
1807
- } else {
1808
- return null;
1809
- }
1810
- }
1811
-
1812
- function sameClass(item1, item2) {
1813
- return getEncodingKey(item1).split("_")[0] === getEncodingKey(item2).split("_")[0];
1814
- }
1815
1760
 
1816
- function getParents(items) {
1817
- let result = [];
1818
- for (let p of items) {
1819
- if (p.parent && result.indexOf(p.parent) < 0)
1820
- result.push(p.parent);
1821
- }
1822
- return result;
1823
- }
1761
+ let cb = [], cellCount = numRows * numCols;
1824
1762
 
1825
- function getAllChildren(cpnt) {
1826
- let result = [];
1827
- if (cpnt.children && cpnt.children.length > 0) {
1828
- for (let c of cpnt.children) {
1829
- result.push(c);
1830
- result = result.concat(getAllChildren(c));
1763
+ switch (this._dir[0]) {
1764
+ case GridLayout.direction.Left2Right:
1765
+ switch (this._dir[1]) {
1766
+ case GridLayout.direction.Top2Bottom:
1767
+ for (let i = 0; i < cellCount; i++) {
1768
+ cb.push(new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
1769
+ this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
1770
+ }
1771
+ break;
1772
+ // return group.children.map((d, i) => new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
1773
+ // this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
1774
+ case GridLayout.direction.Bottom2Top:
1775
+ for (let i = 0; i < cellCount; i++) {
1776
+ cb.push(new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
1777
+ this._top + (this.numRows - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
1778
+ }
1779
+ break;
1780
+ // return group.children.map((d, i) => new Rectangle(this._left + (cellWidth + colGap) * (i%numCols) + leftOffset,
1781
+ // this._top + (this.numRows - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
1782
+ }
1783
+ break;
1784
+ case GridLayout.direction.Right2Left:
1785
+ switch (this._dir[1]) {
1786
+ case GridLayout.direction.Top2Bottom:
1787
+ for (let i = 0; i < cellCount; i++) {
1788
+ cb.push(new Rectangle(leftOffset + this._left + (numCols - 1) * (cellWidth + colGap) - (cellWidth + colGap) * (i%numCols),
1789
+ this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
1790
+ }
1791
+ break;
1792
+ // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (numCols - 1) * (cellWidth + colGap) - (cellWidth + colGap) * (i%numCols),
1793
+ // this._top + (cellHeight + rowGap) * Math.floor(i/numCols), cellWidth, cellHeight));
1794
+ case GridLayout.direction.Bottom2Top: {
1795
+ for (let i = 0; i < cellCount; i++) {
1796
+ cb.push(new Rectangle(leftOffset + this._left + (numCols - 1 - i%numCols) * (cellWidth + colGap),
1797
+ this._top + (this.numRows - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
1798
+ }
1799
+ break;
1800
+ // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (numCols - 1 - i%numCols) * (cellWidth + colGap),
1801
+ // this._top + (this.numRows - 1 - Math.floor(i/numCols)) * (cellHeight + rowGap), cellWidth, cellHeight));
1802
+ }
1803
+ }
1804
+ break;
1805
+ case GridLayout.direction.Top2Bottom:
1806
+ switch (this._dir[1]) {
1807
+ case GridLayout.direction.Left2Right:
1808
+ for (let i = 0; i < cellCount; i++) {
1809
+ cb.push(new Rectangle(leftOffset + this._left + (cellWidth + colGap) * Math.floor(i/this.numRows),
1810
+ this._top + (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
1811
+ }
1812
+ break;
1813
+ // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (cellWidth + colGap) * Math.floor(i/this.numRows),
1814
+ // this._top + (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
1815
+ case GridLayout.direction.Right2Left:
1816
+ for (let i = 0; i < cellCount; i++) {
1817
+ cb.push(new Rectangle(leftOffset + this._left + (cellWidth + colGap) * (this.numCols - 1) - (cellWidth + colGap) * Math.floor(i/this.numRows),
1818
+ this._top + (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
1819
+ }
1820
+ break;
1821
+ // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (cellWidth + colGap) * (this.numCols - 1) - (cellWidth + colGap) * Math.floor(i/this.numRows),
1822
+ // this._top + (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
1823
+ }
1824
+ break;
1825
+ case GridLayout.direction.Bottom2Top:
1826
+ switch (this._dir[1]) {
1827
+ case GridLayout.direction.Left2Right:
1828
+ for (let i = 0; i < cellCount; i++) {
1829
+ cb.push(new Rectangle(leftOffset + this._left + (cellWidth + colGap) * Math.floor(i/this.numRows),
1830
+ this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
1831
+ }
1832
+ break;
1833
+ // this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight)));
1834
+ // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (cellWidth + colGap) * Math.floor(i/this.numRows),
1835
+ // this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
1836
+ case GridLayout.direction.Right2Left:
1837
+ for (let i = 0; i < cellCount; i++) {
1838
+ cb.push(new Rectangle(leftOffset + this._left + (cellWidth + colGap) * (this.numCols - 1) - (cellWidth + colGap) * Math.floor(i/this.numRows),
1839
+ this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
1840
+ }
1841
+ break;
1842
+ // return group.children.map((d, i) => new Rectangle(leftOffset + this._left + (cellWidth + colGap) * (this.numCols - 1) - (cellWidth + colGap) * Math.floor(i/this.numRows),
1843
+ // this._top + (cellHeight + rowGap) * (this.numRows - 1) - (cellHeight + rowGap) * (i%this.numRows), cellWidth, cellHeight));
1844
+ }
1845
+ break;
1831
1846
  }
1832
- }
1833
- return result;
1834
- }
1835
1847
 
1836
- function getLeafMarks(cpnt) {
1837
- let result = [];
1838
- if (isMark(cpnt)) {
1839
- result.push(cpnt);
1840
- } else if (cpnt.children && cpnt.children.length > 0 && !isGuide(cpnt)) {
1841
- for (let c of cpnt.children) {
1842
- result = result.concat(getLeafMarks(c));
1843
- }
1844
- }
1845
- return result;
1846
- }
1848
+ return cb;
1847
1849
 
1848
- function getLeafItems(cpnt) {
1849
- let result = [];
1850
- if (cpnt.children && cpnt.children.length > 0) {
1851
- for (let c of cpnt.children) {
1852
- result = result.concat(getLeafItems(c));
1853
- }
1854
- } else {
1855
- result.push(cpnt);
1850
+ // if (group.firstChild.type === ItemType.Glyph) {
1851
+ // let items = group.firstChild.children, xRanges = [], yRanges = [], minLefts = [], minTops = [], scn = group.getScene();
1852
+ // for (let itm of items) {
1853
+ // let xEnc = scn.positionBound(itm, "x"), wdEnc = scn.sizeBound(itm, "width"),
1854
+ // yEnc = scn.positionBound(itm, "y"), htEnc = scn.sizeBound(itm, "height"),
1855
+ // peers = getPeers(itm, scn);
1856
+ // xRanges.push(xEnc? xEnc.scale.rangeExtent : wdEnc && wdEnc._rectNegativeValues ? wdEnc.scale.rangeExtent : undefined);
1857
+ // yRanges.push(yEnc? yEnc.scale.rangeExtent : htEnc && htEnc._rectNegativeValues ? htEnc.scale.rangeExtent : undefined);
1858
+ // minLefts.push(Math.min(...peers.map(d => d.bounds.left)));
1859
+ // minTops.push(Math.min(...peers.map(d => d.bounds.top)));
1860
+ // }
1861
+ // let wds = [], hts = [];
1862
+ // for (let c of group.children) {
1863
+ // let xCoords = [], yCoords = [];
1864
+ // for (const [i, m] of c.children.entries()) {
1865
+ // xCoords.push( m.refBounds.left, m.refBounds.left + (xRanges[i] ? xRanges[i] : m.bounds.width));
1866
+ // yCoords.push( m.refBounds.top, m.refBounds.top + (yRanges[i] ? yRanges[i] : m.bounds.height));
1867
+ // }
1868
+ // wds.push(Math.max(...xCoords) - Math.min(...xCoords));
1869
+ // hts.push(Math.max(...yCoords) - Math.min(...yCoords));
1870
+ // }
1871
+ // cellWidth = Math.max(...wds);
1872
+ // cellHeight = Math.max(...hts);
1873
+ // } else
1874
+ // if (group.firstChild.type === ItemType.Collection && group.firstChild.layout && group.firstChild.layout.type === LayoutType.Grid) {
1875
+ // let cb = group.firstChild.layout.cellBounds[0];
1876
+ // cellWidth = (cb.width + group.firstChild.layout.colGap) * Math.max(...group.children.map(d => d.layout.numCols)) - group.firstChild.layout.colGap;
1877
+ // cellHeight = (cb.height + group.firstChild.layout.rowGap) * Math.max(...group.children.map(d => d.layout.numRows)) - group.firstChild.layout.rowGap;
1878
+ // } else {
1856
1879
  }
1857
- return result;
1858
- }
1859
1880
 
1860
- function isGuide(item) {
1861
- return item.type === ItemType.Axis || item.type === ItemType.Legend || item.type === ItemType.Gridlines;
1862
- }
1881
+ run() {
1882
+ if (this.group == undefined|| !this.group.children || this.group.children.length === 0)
1883
+ return;
1863
1884
 
1864
- function isMark(cmpnt) {
1865
- return cmpnt instanceof Mark;
1866
- }
1885
+ let cellBounds = this.cellBounds;
1867
1886
 
1868
- function isPath(cmpnt) {
1869
- return cmpnt instanceof Path;
1870
- }
1887
+ let xEncs = this.group.getInternalEncodings("x"),
1888
+ yEncs = this.group.getInternalEncodings("y"),
1889
+ wdEncs = this.group.getInternalEncodings("width"),
1890
+ htEncs = this.group.getInternalEncodings("height");
1891
+ for (let i = 0; i < this.group.children.length; i++) {
1892
+ let c = this.group.children[i];
1893
+ let gridBound = cellBounds[i];
1871
1894
 
1872
- const ItemCounter = {
1873
- "area" : 0,
1874
- "rect" : 0,
1875
- "circle": 0,
1876
- "pie": 0,
1877
- "line": 0,
1878
- "path" : 0,
1879
- "ring" : 0,
1880
- "arc": 0,
1881
- "image": 0,
1882
- "pointText": 0,
1883
- "collection": 0,
1884
- "group": 0,
1885
- "scene": 0,
1886
- "axis": 0,
1887
- "glyph": 0,
1888
- "legend": 0,
1889
- "polygon": 0,
1890
- "gridlines": 0,
1891
- "LinearGradient": 0,
1892
- "link": 0,
1893
- "scale": 0,
1894
- "datatable": 0
1895
- };
1895
+ let dx = gridBound.x - c.bounds.x,
1896
+ dy = gridBound.y - c.bounds.y;
1897
+ c._doTranslate(dx, dy);
1896
1898
 
1897
- function canAlign(items, direction, scene){
1898
- if (direction == Alignment.Top || direction == Alignment.Bottom || direction == Alignment.Middle) {
1899
- for (let item of items) {
1900
- if (!canMoveVertically(item, scene))
1901
- return false;
1899
+ //alignment in cell if c's position is not bound to data
1900
+ let cdx = 0, cdy = 0;
1901
+ if (xEncs.length == 0) {
1902
+ switch(this._cellHorzAlignment) {
1903
+ case Alignment.Left:
1904
+ cdx = gridBound.left - c.bounds.left;
1905
+ break;
1906
+ case Alignment.Center:
1907
+ cdx = gridBound.x - c.bounds.x;
1908
+ break;
1909
+ case Alignment.Right:
1910
+ cdx = gridBound.right - c.bounds.right;
1911
+ break;
1912
+ }
1913
+ }
1914
+
1915
+ if (yEncs.length == 0) {
1916
+ switch(this._cellVertAlignment) {
1917
+ case Alignment.Top:
1918
+ cdy = gridBound.top - c.bounds.top;
1919
+ break;
1920
+ case Alignment.Middle:
1921
+ cdy = gridBound.y - c.bounds.y;
1922
+ break;
1923
+ case Alignment.Bottom:
1924
+ cdy = gridBound.bottom - c.bounds.bottom;
1925
+ break;
1926
+ }
1927
+ }
1928
+
1929
+ c._doTranslate(cdx, cdy);
1902
1930
  }
1903
- return true;
1904
- }
1905
1931
 
1906
- if (direction == Alignment.Left || direction == Alignment.Right || direction == Alignment.Center) {
1907
- for (let item of items) {
1908
- if (!canMoveHorizontally(item, scene))
1909
- return false;
1932
+ if (xEncs.length > 0) {
1933
+ //if childrens' position bound to data, compute position using the scale
1934
+ for (let enc of xEncs)
1935
+ enc._apply();
1936
+ } else if (wdEncs.length > 0) {
1937
+ let enc = wdEncs[wdEncs.length-1];
1938
+ if (enc._rectNegativeValues){
1939
+ enc._apply();
1940
+ }
1910
1941
  }
1911
- return true;
1912
- }
1913
- }
1914
1942
 
1915
- function canMoveHorizontally(item, scene) {
1916
- if (scene.getEncodingByItem(item, "x"))
1917
- return false;
1918
- if (item.parent && item.parent.layout) {
1919
- let layout = item.parent.layout;
1920
- if (layout.type == LayoutType.Grid && layout.numCols > 1) {
1921
- return false;
1943
+ if (yEncs.length > 0) {
1944
+ //yEncs[yEncs.length-1]._map();
1945
+ // yEncs[yEncs.length-1]._apply();
1946
+ for (let enc of yEncs)
1947
+ enc._apply();
1948
+ } else if (htEncs.length > 0) {
1949
+ let enc = htEncs[htEncs.length-1];
1950
+ if (enc._rectNegativeValues){
1951
+ enc._apply();
1952
+ }
1922
1953
  }
1954
+
1955
+ this.group._updateBounds();
1923
1956
  }
1924
- if (item.parent && item.parent.type != ItemType.Scene){
1925
- return canMoveHorizontally(item.parent, scene);
1957
+
1958
+ //TODO: add a corresponding scene level operation, automatically relayout
1959
+ set rowGap(g) {
1960
+ this._rowGap = g;
1961
+ this.run();
1962
+ this.group.getScene()._relayoutAncestors(this.group);
1926
1963
  }
1927
- return true;
1928
- }
1929
1964
 
1930
- function canMoveVertically(item, scene) {
1931
- if (scene.getEncodingByItem(item, "y"))
1932
- return false;
1933
- if (item.parent && item.parent.layout) {
1934
- let layout = item.parent.layout;
1935
- if (layout.type == LayoutType.Grid && layout.numRows > 1) {
1936
- return false;
1937
- }
1965
+ get rowGap() {
1966
+ return this._rowGap;
1938
1967
  }
1939
- if (item.parent && item.parent.type != ItemType.Scene){
1940
- return canMoveVertically(item.parent, scene);
1968
+
1969
+ set colGap(g) {
1970
+ this._colGap = g;
1971
+ this.run();
1972
+ this.group.getScene()._relayoutAncestors(this.group);
1941
1973
  }
1942
- return true;
1943
- }
1944
1974
 
1945
- var CanvasProvider = {
1946
- canvas : undefined,
1975
+ get colGap() {
1976
+ return this._colGap;
1977
+ }
1947
1978
 
1948
- getCanvas: function() {
1949
- if (!window)
1950
- return null;
1951
- if (this.canvas === undefined) {
1952
- this.canvas = document.createElement('canvas');
1979
+ set numCols(c) {
1980
+ if (c < 0 || c > this.group.children.length) {
1981
+ console.warn("Cannot set", c, "columns for", this.group.children.length, "items in grid");
1982
+ return;
1953
1983
  }
1954
- return this.canvas;
1955
- },
1956
-
1957
- getContext: function() {
1958
- var canvas = this.getCanvas();
1959
- return canvas ? canvas.getContext('2d') : null;
1960
- },
1961
- };
1962
-
1963
- var SVGProvider = {
1964
- svg: undefined,
1984
+ this._numCols = c;
1985
+ this._numRows = Math.ceil(this.group.children.length/c);
1986
+ this.run();
1987
+ this.group.getScene()._relayoutAncestors(this.group);
1988
+ }
1965
1989
 
1966
- getSVG: function() {
1967
- if (!window)
1968
- return null;
1969
- if (this.svg === undefined) {
1970
- this.svg = document.createElement('svg');
1990
+ get numCols() {
1991
+ if (this._numCols) {
1992
+ return this._numCols;
1993
+ } else if (this._numRows) {
1994
+ return Math.ceil(this.group.children.length/this._numRows);
1995
+ } else {
1996
+ return 0;
1971
1997
  }
1972
- return this.svg;
1973
1998
  }
1974
- };
1975
-
1976
- // export function getTextWidth(text, font) {
1977
- // let context = CanvasProvider.getContext();
1978
- // context.font = font;
1979
- // let metrics = context.measureText(text);
1980
- // return metrics.width;
1981
- // }
1982
1999
 
1983
- function getTextSize(text, font, fontSize) {
1984
- let context = CanvasProvider.getContext();
1985
- context.font = font;
1986
- let metrics = context.measureText(text);
1987
- if (metrics.fontBoundingBoxAscent)
1988
- return {width: metrics.width, height: metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent};
1989
- else if (metrics.actualBoundingBoxAscent)
1990
- return {width: metrics.width, height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent};
1991
- else
1992
- return {width: metrics.width, height: fontSize};
1993
- }
2000
+ set numRows(c) {
2001
+ if (c < 0 || c > this.group.children.length) {
2002
+ console.warn("Cannot set", c, "rows for", this.group.children.length, "items in grid");
2003
+ return;
2004
+ }
2005
+ this._numRows = c;
2006
+ this._numCols = Math.ceil(this.group.children.length/c);
2007
+ this.run();
2008
+ this.group.getScene()._relayoutAncestors(this.group);
2009
+ }
1994
2010
 
1995
- function getTopLevelGroup(item) {
1996
- let parent = item.parent;
1997
- if (parent.type == ItemType.Scene)
1998
- return item;
1999
- else
2000
- return getTopLevelGroup(parent);
2001
- }
2002
2011
 
2003
- function getTopLevelCollection(item) {
2004
- let parent = item.parent;
2005
- if (item.type == ItemType.Collection) {
2006
- if (parent.type == ItemType.Collection) {
2007
- return getTopLevelCollection(parent);
2008
- } else
2009
- return item;
2010
- } else if (parent.type != ItemType.Scene) {
2011
- return getTopLevelCollection(parent);
2012
- } else {
2013
- return undefined;
2012
+ get numRows() {
2013
+ if (this._numRows) {
2014
+ return this._numRows;
2015
+ } else if (this._numCols) {
2016
+ return Math.ceil(this.group.children.length/this._numCols);
2017
+ } else
2018
+ return 0;
2014
2019
  }
2015
- }
2016
2020
 
2017
- function polar2Cartesian(cx, cy, r, deg){
2018
- let x = r * Math.cos(degree2radian(deg)),
2019
- y = r * Math.sin(degree2radian(deg));
2020
- return [x + cx, cy - y];
2021
- }
2021
+ set vertCellAlignment(v) {
2022
+ if (v != Alignment.Top && v != Alignment.Bottom && v != Alignment.Middle) {
2023
+ throw Errors.UNKOWN_ALIGNMENT;
2024
+ }
2025
+ this._cellVertAlignment = v;
2026
+ this.run();
2027
+ }
2022
2028
 
2023
- function cartesian2Polar(x, y, cx, cy){
2024
- let d = radian2degree(Math.atan2(cy - y, x - cx));
2025
- d = Math.round( d * 10 + Number.EPSILON ) / 10;
2026
- if (d < 0) d += 360;
2027
- let r = Math.sqrt(Math.pow(x-cx, 2) + Math.pow(y-cy, 2));
2028
- r = Math.round( r * 10 + Number.EPSILON ) / 10;
2029
- return [d, r];
2030
- }
2029
+ get vertCellAlignment() {
2030
+ return this._cellVertAlignment;
2031
+ }
2031
2032
 
2032
- function degree2radian(d){
2033
- return d * Math.PI/180;
2034
- }
2033
+ set horzCellAlignment(h) {
2034
+ if (h != Alignment.Left && h != Alignment.Center && h != Alignment.Right) {
2035
+ throw Errors.UNKOWN_ALIGNMENT;
2036
+ }
2037
+ this._cellHorzAlignment = h;
2038
+ this.run();
2039
+ }
2035
2040
 
2036
- function radian2degree(r){
2037
- return r * 180 / Math.PI;
2038
- }
2041
+ get horzCellAlignment() {
2042
+ return this._cellHorzAlignment;
2043
+ }
2039
2044
 
2040
- function CheckAreaOrien(area){
2041
- let VNum = area.vertices.length;
2042
- for (let i = 0; i < area.vertices.length / 2; i++) {
2043
- let Vid1 = i, Vid2 = VNum - i - 1;
2044
- let peer1 = area.vertices[Vid1], peer2 = area.vertices[Vid2];
2045
- if (peer1.x == peer2.x && peer1.y == peer2.y) {
2046
- continue;
2045
+ //accepts two formats: a two-element array, or a string
2046
+ set direction(d) {
2047
+ if (Array.isArray(d) && d.length === 2) {
2048
+ this._dir = d;
2047
2049
  } else {
2048
- if (peer1.x == peer2.x) {
2049
- return "horizontal";
2050
- } else {
2051
- return "vertical";
2052
- }
2050
+ this._dir = d.split("_");
2053
2051
  }
2052
+ this.run();
2053
+ }
2054
+
2055
+ get direction() {
2056
+ return this._dir.join("_");
2054
2057
  }
2055
2058
  }
2056
2059
 
2060
+ GridLayout.direction = {
2061
+ Left2Right: "l2r",
2062
+ Right2Left: "r2l",
2063
+ Top2Bottom: "t2b",
2064
+ Bottom2Top: "b2t"
2065
+ };
2066
+
2057
2067
  /**
2058
2068
  * Same as group in graphical design tools
2059
2069
  **/
@@ -4006,6 +4016,7 @@
4006
4016
  this.scale = createScale("time");
4007
4017
  this.scale.isFlipped = this._flipScale;
4008
4018
  range = [0, extent];
4019
+ this.scale._baseItem = this.anyItem;
4009
4020
  }
4010
4021
  break;
4011
4022
 
@@ -4026,6 +4037,7 @@
4026
4037
  this.scale = createScale("point");
4027
4038
  this.scale.isFlipped = this._flipScale;
4028
4039
  range = [0, extent];
4040
+ this.scale._baseItem = this.anyItem;
4029
4041
  }
4030
4042
  break;
4031
4043
 
@@ -6188,6 +6200,8 @@
6188
6200
  matches(item) {}
6189
6201
 
6190
6202
  reposition() {
6203
+ if (this instanceof EncodingAxis)
6204
+ this._determineAxisFlip();
6191
6205
  this._positionPath();
6192
6206
  this._positionTicks();
6193
6207
  this._positionLabels();
@@ -10747,12 +10761,18 @@
10747
10761
  .attr("width", d => d.width).attr("height", rowGap)
10748
10762
  .style("fill", "pink").style("opacity", 0.5)
10749
10763
  ;
10750
- let left = Math.min(...cellBounds.map(d => d.left)),
10751
- top = Math.min(...cellBounds.map(d => d.top));
10752
- this._decoMap[gridId].append("rect").attr("x", left).attr("y", top)
10753
- .attr("width", c.bounds.width).attr("height", c.bounds.height)
10764
+ for (let cb of cellBounds) {
10765
+ this._decoMap[gridId].append("rect").attr("x", cb.left).attr("y", cb.top)
10766
+ .attr("width", cb.width).attr("height", cb.height)
10754
10767
  .attr("stroke", "blue").attr("stroke-width", "1px")
10755
10768
  .attr("stroke-dasharray", "5,5").attr("fill", "none");
10769
+ }
10770
+ // let left = Math.min(...cellBounds.map(d => d.left)),
10771
+ // top = Math.min(...cellBounds.map(d => d.top))
10772
+ // this._decoMap[gridId].append("rect").attr("x", left).attr("y", top)
10773
+ // .attr("width", c.bounds.width).attr("height", c.bounds.height)
10774
+ // .attr("stroke", "blue").attr("stroke-width", "1px")
10775
+ // .attr("stroke-dasharray", "5,5").attr("fill", "none");
10756
10776
 
10757
10777
  }
10758
10778
 
@@ -24324,6 +24344,14 @@
24324
24344
  }
24325
24345
  }
24326
24346
 
24347
+ function canFormGlyph(args) {
24348
+ for (let itm of args) {
24349
+ if (!isMark(itm))
24350
+ return false;
24351
+ }
24352
+ return true;
24353
+ }
24354
+
24327
24355
  function canClassify(item) {
24328
24356
  if (item.type !== ItemType.Collection) return false;
24329
24357
  if (item.children.length < 2) return false;
@@ -24349,6 +24377,7 @@
24349
24377
  exports.canClassify = canClassify;
24350
24378
  exports.canDensify = canDensify;
24351
24379
  exports.canDivide = canDivide;
24380
+ exports.canFormGlyph = canFormGlyph;
24352
24381
  exports.canRepeat = canRepeat;
24353
24382
  exports.cartesianToPolar = cartesianToPolar;
24354
24383
  exports.createScale = createScale;