mascot-vis 1.11.2 → 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 +21 -21
  3. package/dist/mascot.js +1377 -1204
  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.11.2
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,1255 +273,846 @@
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;
898
-
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;
904
-
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
- }
912
-
913
- // Based on basic.Point.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
914
-
915
- class Point {
916
-
917
- constructor(x, y) {
918
- this.x = x;
919
- this.y = y;
920
- }
921
-
922
- transform(matrix) {
923
- return matrix ? matrix._transformPoint(this) : this;
925
+ get strokeDash() {
926
+ return this.styles["strokeDash"];
924
927
  }
925
928
 
926
- negate() {
927
- return new Point(-this.x, -this.y);
928
- }
929
-
930
- subtract(point) {
931
- return new Point(this.x - point.x, this.y - point.y);
929
+ set strokeDash(c) {
930
+ this.styles["strokeDash"] = c;
932
931
  }
933
932
 
934
- isZero() {
935
- return isZero(this.x) && isZero(this.y)
936
- }
937
-
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
- }
953
-
954
- }
955
-
956
- // Based on path.Segment.js, as part of Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
957
-
958
- class Vertex {
959
-
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;
968
-
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;
933
+ _doTranslate(dx, dy) {
934
+ for (let v of this.vertices) {
935
+ v._doTranslate(dx, dy);
936
+ }
937
+ this._updateBounds();
978
938
  }
979
939
 
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);
986
- default:
987
- return new Rectangle(this.x - 0.5, this.y - 0.5, 1, 1);
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
+ }
988
960
  }
961
+ this._updateBounds();
989
962
  }
990
963
 
991
- get id() {
992
- return this.parent.id + "_v_" + this._id;
993
- }
964
+ _updateBounds() {
965
+ let vx = this.vertices.map(d => d.x),
966
+ vy = this.vertices.map(d => d.y);
994
967
 
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;
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
+ }
1014
978
  }
1015
979
 
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;
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
1031
984
  }
1032
985
 
1033
- _doTranslate(dx, dy) {
1034
- this.x += dx;
1035
- this.y += dy;
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
+ }
1036
995
  }
1037
996
 
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();
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];
1042
1010
  }
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;
1052
1011
  }
1053
1012
 
1054
- set polarAngle(a) {
1055
- this._polarAngle = a;
1056
- }
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();
1057
1023
 
1058
- get polarAngle() {
1059
- return this._polarAngle;
1024
+ return p._;
1060
1025
  }
1061
- }
1026
+
1027
+ // toSVG() {
1062
1028
 
1063
- Vertex.styles = ["vxShape", "vxWidth", "vxHeight", "vxRadius", "vxFillColor", "vxStrokeColor", "vxStrokeWidth", "vxOpacity"];
1029
+ // }
1064
1030
 
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;
1031
+ // fromSVG() {
1072
1032
 
1073
- this.dataScope = undefined;
1074
- this.parent = parentMark;
1075
- }
1033
+ // }
1076
1034
 
1077
- get id() {
1078
- return this.parent.id + "_s_" + this._id;
1035
+ get firstVertex() {
1036
+ return this.vertices[0];
1079
1037
  }
1080
1038
 
1081
- _doTranslate(dx, dy) {
1082
- this.vertex1._doTranslate(dx, dy);
1083
- this.vertex2._doTranslate(dx, dy);
1039
+ get firstSegment() {
1040
+ return this.segments[0];
1084
1041
  }
1085
1042
 
1086
- get x() {
1087
- return (this.vertex1.x + this.vertex2.x)/2;
1043
+ get lastVertex() {
1044
+ return this.vertices[this.vertices.length - 1];
1088
1045
  }
1089
1046
 
1090
- get y() {
1091
- return (this.vertex1.y + this.vertex2.y)/2;
1047
+ get lastSegment() {
1048
+ return this.segments[this.segments.length - 1];
1092
1049
  }
1093
- }
1094
1050
 
1095
- class Path extends Mark {
1096
-
1097
- constructor(args) {
1098
- super(args);
1099
- this.type = "type" in args ? args.type : ItemType.Path;
1100
-
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";
1107
-
1108
- this.vertices = [];
1109
- this.vertexCounter = 0; //for assigning vertex ids
1110
- this.segmentCounter = 0;
1111
- this.segments = [];
1112
-
1113
- this.anchor = undefined;
1114
-
1115
- this.closed = false;
1116
-
1117
- this.curveMode = "linear";
1118
-
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};
1121
-
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;
1130
-
1131
- if (args !== undefined) {
1132
- for (let vs of Vertex.styles){
1133
- if (vs in args)
1134
- this["_" + vs] = args[vs];
1135
- }
1136
-
1137
- if ("vertices" in args) {
1138
- this._setVertices(args["vertices"]);
1139
- }
1140
- }
1141
- }
1142
-
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;
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;
1168
1069
  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];
1070
+ return d3__namespace.curveLinear;
1226
1071
  }
1227
- return json;
1228
1072
  }
1229
1073
 
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;
1238
- }
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
- }
1249
- }
1074
+ get vxShape(){
1075
+ return this._vxShape;
1076
+ }
1250
1077
 
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
- }
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++));
1262
- }
1078
+ set vxShape(s){
1079
+ this._vxShape = s;
1080
+ for (let v of this.vertices)
1081
+ v.shape = s;
1263
1082
  }
1264
1083
 
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++));
1284
- }
1285
- if (target.closed)
1286
- target.segments.push(new Segment(target.vertices[target.vertices.length-1], target.vertices[0], target, target.segmentCounter++));
1084
+ get vxWidth(){
1085
+ return this._vxWidth;
1287
1086
  }
1288
1087
 
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;
1088
+ set vxWidth(s){
1089
+ this._vxWidth = s;
1090
+ for (let v of this.vertices)
1091
+ v.width = s;
1296
1092
  }
1297
1093
 
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);
1094
+ get vxHeight(){
1095
+ return this._vxHeight;
1307
1096
  }
1308
1097
 
1309
- get x() {
1310
- return this.bounds.x;
1098
+ set vxHeight(s){
1099
+ this._vxHeight = s;
1100
+ for (let v of this.vertices)
1101
+ v.height = s;
1311
1102
  }
1312
1103
 
1313
- get y() {
1314
- return this.bounds.y;
1104
+ get vxRadius(){
1105
+ return this._vxRadius;
1315
1106
  }
1316
1107
 
1317
- get strokeColor() {
1318
- return this.styles["strokeColor"];
1108
+ set vxRadius(s){
1109
+ this._vxRadius = s;
1110
+ for (let v of this.vertices)
1111
+ v.radius = s;
1319
1112
  }
1320
1113
 
1321
- set strokeColor(c) {
1322
- this.styles["strokeColor"] = c;
1323
- }
1324
-
1325
- get strokeWidth() {
1326
- return this.styles["strokeWidth"];
1327
- }
1328
-
1329
- set strokeWidth(c) {
1330
- this.styles["strokeWidth"] = c;
1331
- }
1332
-
1333
- get fillColor() {
1334
- return this.styles["fillColor"];
1335
- }
1336
-
1337
- set fillColor(c) {
1338
- this.styles["fillColor"] = c;
1339
- }
1340
-
1341
- get strokeDash() {
1342
- return this.styles["strokeDash"];
1343
- }
1344
-
1345
- set strokeDash(c) {
1346
- this.styles["strokeDash"] = c;
1347
- }
1348
-
1349
- _doTranslate(dx, dy) {
1350
- for (let v of this.vertices) {
1351
- v._doTranslate(dx, dy);
1352
- }
1353
- this._updateBounds();
1354
- }
1355
-
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
- }
1376
- }
1377
- this._updateBounds();
1378
- }
1379
-
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
- this._bounds = new Rectangle(left, top, right - left, btm - top);
1386
- }
1387
-
1388
- addVertex(x, y, i) {
1389
- let vertex = new Vertex(new Point(x, y), this, this.vertexCounter++);
1390
- this.vertices.splice(i, 0, vertex);
1391
- //TODO: handle segments
1392
- }
1393
-
1394
- sortVertices(channel, descending) {
1395
- this.vertices.sort((a,b) => a[channel] - b[channel]);
1396
- if (descending)
1397
- this.vertices.reverse();
1398
- for (let i = 0; i < this.segments.length; i++) {
1399
- let segment = this.segments[i];
1400
- segment.vertex1 = this.vertices[i];
1401
- segment.vertex2 = this.vertices[(i+1)%this.vertices.length];
1402
- }
1403
- }
1404
-
1405
- sortVerticesByData(field, descending, order) {
1406
- let f;
1407
- if (order)
1408
- f = (a, b) => order.indexOf(a.dataScope.getFieldValue(field)) - order.indexOf(b.dataScope.getFieldValue(field));
1409
- else
1410
- f = (a, b) => (a.dataScope.getFieldValue(field) < b.dataScope.getFieldValue(field) ? -1 : 1 );
1411
- this.vertices.sort(f);
1412
- if (descending)
1413
- this.vertices.reverse();
1414
- for (let i = 0; i < this.segments.length; i++) {
1415
- let segment = this.segments[i];
1416
- segment.vertex1 = this.vertices[i];
1417
- segment.vertex2 = this.vertices[(i+1)%this.vertices.length];
1418
- }
1419
- }
1420
-
1421
- getSVGPathData() {
1422
- let p = d3__namespace.path();
1423
- let curve = this._getD3CurveFunction(this.curveMode)(p);
1424
- curve.lineStart();
1425
- for (let vertex of this.vertices) {
1426
- curve.point(vertex.x, vertex.y);
1427
- }
1428
- if (this.closed)
1429
- curve.point(this.vertices[0].x, this.vertices[0].y);
1430
- curve.lineEnd();
1431
-
1432
- return p._;
1433
- }
1434
-
1435
- // toSVG() {
1436
-
1437
- // }
1438
-
1439
- // fromSVG() {
1440
-
1441
- // }
1442
-
1443
- get firstVertex() {
1444
- return this.vertices[0];
1445
- }
1446
-
1447
- get firstSegment() {
1448
- return this.segments[0];
1449
- }
1450
-
1451
- get lastVertex() {
1452
- return this.vertices[this.vertices.length - 1];
1453
- }
1454
-
1455
- get lastSegment() {
1456
- return this.segments[this.segments.length - 1];
1457
- }
1458
-
1459
- _getD3CurveFunction(v){
1460
- switch(v) {
1461
- case CurveMode.Natural:
1462
- return d3__namespace.curveNatural;
1463
- case CurveMode.Basis:
1464
- return d3__namespace.curveBasis;
1465
- case CurveMode.BumpX:
1466
- return d3__namespace.curveBumpX;
1467
- case CurveMode.BumpY:
1468
- return d3__namespace.curveBumpY;
1469
- case CurveMode.Linear:
1470
- return d3__namespace.curveLinear;
1471
- case CurveMode.Step:
1472
- return d3__namespace.curveStep;
1473
- case CurveMode.CatmullRom:
1474
- return d3__namespace.curveCatmullRom;
1475
- case CurveMode.Cardinal:
1476
- return d3__namespace.curveCardinal;
1477
- default:
1478
- return d3__namespace.curveLinear;
1479
- }
1480
- }
1481
-
1482
- get vxShape(){
1483
- return this._vxShape;
1484
- }
1485
-
1486
- set vxShape(s){
1487
- this._vxShape = s;
1488
- for (let v of this.vertices)
1489
- v.shape = s;
1490
- }
1491
-
1492
- get vxWidth(){
1493
- return this._vxWidth;
1494
- }
1495
-
1496
- set vxWidth(s){
1497
- this._vxWidth = s;
1498
- for (let v of this.vertices)
1499
- v.width = s;
1500
- }
1501
-
1502
- get vxHeight(){
1503
- return this._vxHeight;
1504
- }
1505
-
1506
- set vxHeight(s){
1507
- this._vxHeight = s;
1508
- for (let v of this.vertices)
1509
- v.height = s;
1510
- }
1511
-
1512
- get vxRadius(){
1513
- return this._vxRadius;
1514
- }
1515
-
1516
- set vxRadius(s){
1517
- this._vxRadius = s;
1518
- for (let v of this.vertices)
1519
- v.radius = s;
1520
- }
1521
-
1522
- get vxFillColor(){
1523
- return this._vxFillColor;
1114
+ get vxFillColor(){
1115
+ return this._vxFillColor;
1524
1116
  }
1525
1117
 
1526
1118
  set vxFillColor(s){
@@ -1744,6 +1336,18 @@
1744
1336
  return undefined;
1745
1337
  }
1746
1338
 
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;
1347
+ }
1348
+ return undefined;
1349
+ }
1350
+
1747
1351
  function getCellBoundsInGridLayout(item) {
1748
1352
  let itm = item, parent = item.parent;
1749
1353
  while (parent && parent.type != ItemType.Scene) {
@@ -1789,6 +1393,10 @@
1789
1393
  }
1790
1394
  }
1791
1395
 
1396
+ function sameClass(item1, item2) {
1397
+ return getEncodingKey(item1).split("_")[0] === getEncodingKey(item2).split("_")[0];
1398
+ }
1399
+
1792
1400
  function getParents(items) {
1793
1401
  let result = [];
1794
1402
  for (let p of items) {
@@ -1930,106 +1538,532 @@
1930
1538
  return this.canvas;
1931
1539
  },
1932
1540
 
1933
- getContext: function() {
1934
- var canvas = this.getCanvas();
1935
- return canvas ? canvas.getContext('2d') : null;
1936
- },
1937
- };
1541
+ getContext: function() {
1542
+ var canvas = this.getCanvas();
1543
+ return canvas ? canvas.getContext('2d') : null;
1544
+ },
1545
+ };
1546
+
1547
+ var SVGProvider = {
1548
+ svg: undefined,
1549
+
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;
1557
+ }
1558
+ };
1559
+
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
+ // }
1566
+
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
+ }
1578
+
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;
1598
+ }
1599
+ }
1600
+
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
+ }
1606
+
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
+ }
1615
+
1616
+ function degree2radian(d){
1617
+ return d * Math.PI/180;
1618
+ }
1619
+
1620
+ function radian2degree(r){
1621
+ return r * 180 / Math.PI;
1622
+ }
1623
+
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
+ }
1638
+ }
1639
+ }
1640
+
1641
+ class Layout {
1642
+
1643
+ constructor(args){
1644
+ this.group = undefined;
1645
+ }
1646
+
1647
+ run(){}
1648
+
1649
+ clone(){}
1650
+ }
1651
+
1652
+ class GridLayout extends Layout {
1653
+
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;
1668
+ }
1669
+
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;
1678
+
1679
+ }
1680
+
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;
1694
+ }
1695
+
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
+ });
1706
+ }
1707
+
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);
1716
+ }
1717
+
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);
1724
+ }
1725
+
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;
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];
1749
+ }
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;
1759
+ }
1760
+
1761
+ let cb = [], cellCount = numRows * numCols;
1762
+
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;
1846
+ }
1847
+
1848
+ return cb;
1849
+
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 {
1879
+ }
1880
+
1881
+ run() {
1882
+ if (this.group == undefined|| !this.group.children || this.group.children.length === 0)
1883
+ return;
1884
+
1885
+ let cellBounds = this.cellBounds;
1886
+
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];
1894
+
1895
+ let dx = gridBound.x - c.bounds.x,
1896
+ dy = gridBound.y - c.bounds.y;
1897
+ c._doTranslate(dx, dy);
1898
+
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);
1930
+ }
1931
+
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
+ }
1941
+ }
1942
+
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
+ }
1953
+ }
1954
+
1955
+ this.group._updateBounds();
1956
+ }
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);
1963
+ }
1964
+
1965
+ get rowGap() {
1966
+ return this._rowGap;
1967
+ }
1938
1968
 
1939
- var SVGProvider = {
1940
- svg: undefined,
1969
+ set colGap(g) {
1970
+ this._colGap = g;
1971
+ this.run();
1972
+ this.group.getScene()._relayoutAncestors(this.group);
1973
+ }
1941
1974
 
1942
- getSVG: function() {
1943
- if (!window)
1944
- return null;
1945
- if (this.svg === undefined) {
1946
- this.svg = document.createElement('svg');
1975
+ get colGap() {
1976
+ return this._colGap;
1977
+ }
1978
+
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;
1947
1983
  }
1948
- return this.svg;
1984
+ this._numCols = c;
1985
+ this._numRows = Math.ceil(this.group.children.length/c);
1986
+ this.run();
1987
+ this.group.getScene()._relayoutAncestors(this.group);
1949
1988
  }
1950
- };
1951
1989
 
1952
- // export function getTextWidth(text, font) {
1953
- // let context = CanvasProvider.getContext();
1954
- // context.font = font;
1955
- // let metrics = context.measureText(text);
1956
- // return metrics.width;
1957
- // }
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;
1997
+ }
1998
+ }
1958
1999
 
1959
- function getTextSize(text, font, fontSize) {
1960
- let context = CanvasProvider.getContext();
1961
- context.font = font;
1962
- let metrics = context.measureText(text);
1963
- if (metrics.fontBoundingBoxAscent)
1964
- return {width: metrics.width, height: metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent};
1965
- else if (metrics.actualBoundingBoxAscent)
1966
- return {width: metrics.width, height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent};
1967
- else
1968
- return {width: metrics.width, height: fontSize};
1969
- }
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
+ }
1970
2010
 
1971
- function getTopLevelGroup(item) {
1972
- let parent = item.parent;
1973
- if (parent.type == ItemType.Scene)
1974
- return item;
1975
- else
1976
- return getTopLevelGroup(parent);
1977
- }
1978
2011
 
1979
- function getTopLevelCollection(item) {
1980
- let parent = item.parent;
1981
- if (item.type == ItemType.Collection) {
1982
- if (parent.type == ItemType.Collection) {
1983
- return getTopLevelCollection(parent);
1984
- } else
1985
- return item;
1986
- } else if (parent.type != ItemType.Scene) {
1987
- return getTopLevelCollection(parent);
1988
- } else {
1989
- 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;
1990
2019
  }
1991
- }
1992
2020
 
1993
- function polar2Cartesian(cx, cy, r, deg){
1994
- let x = r * Math.cos(degree2radian(deg)),
1995
- y = r * Math.sin(degree2radian(deg));
1996
- return [x + cx, cy - y];
1997
- }
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
+ }
1998
2028
 
1999
- function cartesian2Polar(x, y, cx, cy){
2000
- let d = radian2degree(Math.atan2(cy - y, x - cx));
2001
- d = Math.round( d * 10 + Number.EPSILON ) / 10;
2002
- if (d < 0) d += 360;
2003
- let r = Math.sqrt(Math.pow(x-cx, 2) + Math.pow(y-cy, 2));
2004
- r = Math.round( r * 10 + Number.EPSILON ) / 10;
2005
- return [d, r];
2006
- }
2029
+ get vertCellAlignment() {
2030
+ return this._cellVertAlignment;
2031
+ }
2007
2032
 
2008
- function degree2radian(d){
2009
- return d * Math.PI/180;
2010
- }
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
+ }
2011
2040
 
2012
- function radian2degree(r){
2013
- return r * 180 / Math.PI;
2014
- }
2041
+ get horzCellAlignment() {
2042
+ return this._cellHorzAlignment;
2043
+ }
2015
2044
 
2016
- function CheckAreaOrien(area){
2017
- let VNum = area.vertices.length;
2018
- for (let i = 0; i < area.vertices.length / 2; i++) {
2019
- let Vid1 = i, Vid2 = VNum - i - 1;
2020
- let peer1 = area.vertices[Vid1], peer2 = area.vertices[Vid2];
2021
- if (peer1.x == peer2.x && peer1.y == peer2.y) {
2022
- 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;
2023
2049
  } else {
2024
- if (peer1.x == peer2.x) {
2025
- return "horizontal";
2026
- } else {
2027
- return "vertical";
2028
- }
2050
+ this._dir = d.split("_");
2029
2051
  }
2052
+ this.run();
2053
+ }
2054
+
2055
+ get direction() {
2056
+ return this._dir.join("_");
2030
2057
  }
2031
2058
  }
2032
2059
 
2060
+ GridLayout.direction = {
2061
+ Left2Right: "l2r",
2062
+ Right2Left: "r2l",
2063
+ Top2Bottom: "t2b",
2064
+ Bottom2Top: "b2t"
2065
+ };
2066
+
2033
2067
  /**
2034
2068
  * Same as group in graphical design tools
2035
2069
  **/
@@ -2470,6 +2504,10 @@
2470
2504
  _updateTuples(field, value) {
2471
2505
  this._tuples = this._tuples.filter(d => d[field] == value);
2472
2506
  }
2507
+
2508
+ get tuples() {
2509
+ return this._tuples;
2510
+ }
2473
2511
  }
2474
2512
 
2475
2513
  function repeatItem(scene, compnt, field, datatable, callback) {
@@ -2756,13 +2794,22 @@
2756
2794
  run() {
2757
2795
  if (this.group == undefined || !this.group.children || this.group.children.length === 0)
2758
2796
  return;
2759
- if (this.group.children[0].type == "area") {
2760
- this._stackAreas();
2761
- } else if (this.group.children[0].type == ItemType.Arc || this.group.children[0].type == ItemType.Pie) {
2762
- this._stackArcs();
2763
- } else {
2764
- this._stackRects();
2765
- }
2797
+ let leafMark = getLeafMarks(this.group)[0];
2798
+ switch (leafMark.type) {
2799
+ case ItemType.Area:
2800
+ this._stackAreas();
2801
+ break;
2802
+ case ItemType.Arc:
2803
+ case ItemType.Pie:
2804
+ if (leafMark.parent === this.group)
2805
+ this._stackArcs();
2806
+ break;
2807
+ case ItemType.Rect:
2808
+ case ItemType.Image:
2809
+ case ItemType.Circle:
2810
+ this._stackRects();
2811
+ break;
2812
+ }
2766
2813
  }
2767
2814
 
2768
2815
  set vertCellAlignment(v) {
@@ -2818,6 +2865,8 @@
2818
2865
  switch (compnt.type) {
2819
2866
  case ItemType.Line:
2820
2867
  return _doLineDivide(scene, compnt, f, datatable);
2868
+ case ItemType.Path:
2869
+ return _doPathDivide(scene, compnt, f, datatable);
2821
2870
  case ItemType.Circle:
2822
2871
  return _doCircleDivide(scene, compnt, orientation, f, datatable);
2823
2872
  case ItemType.Rect:
@@ -2892,6 +2941,57 @@
2892
2941
  return toReturn;
2893
2942
  }
2894
2943
 
2944
+ function _doPathDivide(scene, compnt, field, datatable) {
2945
+ let peers = getPeers(compnt, scene);
2946
+ let toReturn;
2947
+ let ds = datatable.getFieldSummary(field).unique.map(d => new DataScope(datatable).cross(field, d));
2948
+
2949
+ let line2Scopes = {}, max = 0;
2950
+ for (let p of peers) {
2951
+ let scopes = ds;
2952
+ if (p.dataScope) {
2953
+ scopes = ds.map(d => d.merge(p.dataScope));
2954
+ scopes = scopes.filter(d => !d.isEmpty());
2955
+ }
2956
+ if (scopes.length > max)
2957
+ max = scopes.length;
2958
+ line2Scopes[p.id] = scopes;
2959
+ }
2960
+ let collClassId;
2961
+ for (let p of peers) {
2962
+ let coll = scene.collection();
2963
+ if (collClassId == undefined)
2964
+ collClassId = coll.id;
2965
+ coll.classId = collClassId;
2966
+ coll.dataScope = p.dataScope;
2967
+
2968
+ let parent = p.parent;
2969
+ //let index = parent.children.indexOf(p) - 1;
2970
+ parent.addChild(coll);
2971
+
2972
+ let scopes = line2Scopes[p.id];
2973
+ let x1 = p.bounds.left, y1 = p.bounds.top, x2 = p.bounds.right, y2 = p.bounds.bottom;
2974
+
2975
+ for (let i = 0; i < scopes.length; i++) {
2976
+ let c = scene.mark("line", {x1: x1 + (x2 - x1) * i /max, x2: x1 + (x2 - x1) * (i + 1)/max, y1: y1 + (y2 - y1) * i /max, y2: y1 + (y2 - y1) * (i + 1)/max});
2977
+ c.classId = compnt.id;
2978
+ c.dataScope = scopes[i];
2979
+ coll.addChild(c);
2980
+ }
2981
+
2982
+ parent.removeChild(p);
2983
+
2984
+ if (p == compnt)
2985
+ toReturn = coll;
2986
+ }
2987
+
2988
+ let f = compnt.firstVertex.dataScope.fields[0];
2989
+ scene.densify(toReturn.firstChild, datatable, {field: f});
2990
+
2991
+
2992
+ return toReturn;
2993
+ }
2994
+
2895
2995
 
2896
2996
 
2897
2997
  function _doRectDivide(scene, compnt, o, field, datatable) {
@@ -3916,6 +4016,7 @@
3916
4016
  this.scale = createScale("time");
3917
4017
  this.scale.isFlipped = this._flipScale;
3918
4018
  range = [0, extent];
4019
+ this.scale._baseItem = this.anyItem;
3919
4020
  }
3920
4021
  break;
3921
4022
 
@@ -3936,6 +4037,7 @@
3936
4037
  this.scale = createScale("point");
3937
4038
  this.scale.isFlipped = this._flipScale;
3938
4039
  range = [0, extent];
4040
+ this.scale._baseItem = this.anyItem;
3939
4041
  }
3940
4042
  break;
3941
4043
 
@@ -3958,8 +4060,11 @@
3958
4060
  this.scale = createScale(this.scaleType);
3959
4061
  this.scale.isFlipped = this._flipScale;
3960
4062
  this.scale.includeZero = this._includeZero;
3961
- //domain = this._includeZero ? [0, max] : [min, max];
3962
4063
  range = [0, extent];
4064
+ //remember the item that was used to first create this scale,
4065
+ //so that when the scale is reused later, we can refer to the base item
4066
+ //to get absoluate positions
4067
+ this.scale._baseItem = this.anyItem;
3963
4068
  }
3964
4069
  if (domain[0] == domain[1])
3965
4070
  domain[1] = domain[0] * 1.1;
@@ -3980,9 +4085,26 @@
3980
4085
  for (let enc of this.scale.encodings)
3981
4086
  items = items.concat(enc.items);
3982
4087
  if (channel == "x") {
3983
- let layout = getClosestLayout(this.anyItem, "grid");
4088
+ //let layout = getClosestLayout(this.anyItem, "grid");
3984
4089
  //let layout = getTopLevelCollection(this.anyItem) ? getTopLevelCollection(this.anyItem).layout : getClosestLayout(this.anyItem);
3985
- if (layout && layout.type == LayoutType.Grid){
4090
+ let layout = getClosestLayout(this.anyItem, "grid"), baseLayout = this.scale._baseItem ? getClosestLayout(this.scale._baseItem, "grid"): undefined;
4091
+ if (this.scale._baseItem && !sameClass(this.anyItem, this.scale._baseItem) && layout && baseLayout && layout.numRows === baseLayout.numRows && layout.numCols === baseLayout.numCols) {
4092
+ let tx = baseLayout.group.bounds.left - layout.group.bounds.left,
4093
+ ty = 0;
4094
+ layout.group.getScene().translate(layout.group, tx, ty);
4095
+ layout._colGap = baseLayout.colGap;
4096
+ let cellIndices = this.items.map(d => getCellIndexInLayout(d)),
4097
+ baseCellBounds = baseLayout.cellBounds;
4098
+ for (let i = 0; i < this.items.length; i++) {
4099
+ let itm = this.items[i];
4100
+ let dx = baseCellBounds[cellIndices[i]].left + this.scale.map(this.data[i]) - itm[channel],
4101
+ dy = 0;
4102
+ itm._doTranslate(dx, dy);
4103
+ if (itm.type == "vertex" || itm.type == "segment")
4104
+ itm.parent._updateBounds();
4105
+ }
4106
+ this.anyItem.parent.getScene()._updateAncestorBounds(this.anyItem, this.items);
4107
+ } else if (layout && layout.type == LayoutType.Grid){
3986
4108
  //do not use scale.offset, use cell bounds
3987
4109
  for (let i = 0; i < this.items.length; i++) {
3988
4110
  let itm = this.items[i], itmCb = getCellBoundsInLayout(itm);
@@ -4031,9 +4153,27 @@
4031
4153
  }
4032
4154
 
4033
4155
  } else {//channel y
4034
- let layout = getClosestLayout(this.anyItem, "grid");
4035
4156
  //let layout = getTopLevelCollection(this.anyItem) ? getTopLevelCollection(this.anyItem).layout : getClosestLayout(this.anyItem);
4036
- if (layout && layout.type == LayoutType.Grid){
4157
+ let layout = getClosestLayout(this.anyItem, "grid"), baseLayout = this.scale._baseItem ? getClosestLayout(this.scale._baseItem, "grid"): undefined;
4158
+ if (this.scale._baseItem && !sameClass(this.anyItem, this.scale._baseItem) && layout && baseLayout && layout.numRows === baseLayout.numRows && layout.numCols === baseLayout.numCols) {
4159
+ //if (this.scale._baseItem && !sameClass(this.anyItem, this.scale._baseItem) && baseLayout && baseLayout.numRows === 1) {
4160
+ //let peers = getPeers(this.scale._baseItem, this.scale._baseItem.parent.getScene());
4161
+ let tx = 0,
4162
+ ty = baseLayout.group.bounds.top - layout.group.bounds.top;
4163
+ layout.group.getScene().translate(layout.group, tx, ty);
4164
+ layout._rowGap = baseLayout.rowGap;
4165
+ let cellIndices = this.items.map(d => getCellIndexInLayout(d)),
4166
+ baseCellBounds = baseLayout.cellBounds;
4167
+ for (let i = 0; i < this.items.length; i++) {
4168
+ let itm = this.items[i];
4169
+ let dx = 0,
4170
+ dy = baseCellBounds[cellIndices[i]].bottom - this.scale.map(this.data[i]) - itm[channel];
4171
+ itm._doTranslate(dx, dy);
4172
+ if (itm.type == "vertex" || itm.type == "segment")
4173
+ itm.parent._updateBounds();
4174
+ }
4175
+ this.anyItem.parent.getScene()._updateAncestorBounds(this.anyItem, this.items);
4176
+ } else if (layout && layout.type == LayoutType.Grid){
4037
4177
  let cellBounds = this.items.map(d => getCellBoundsInLayout(d));
4038
4178
  for (let i = 0; i < this.items.length; i++) {
4039
4179
  let itm = this.items[i];
@@ -6060,6 +6200,8 @@
6060
6200
  matches(item) {}
6061
6201
 
6062
6202
  reposition() {
6203
+ if (this instanceof EncodingAxis)
6204
+ this._determineAxisFlip();
6063
6205
  this._positionPath();
6064
6206
  this._positionTicks();
6065
6207
  this._positionLabels();
@@ -6587,6 +6729,7 @@
6587
6729
  this._orientation = "orientation" in args ? args["orientation"] :
6588
6730
  this._channel === "x" || this._channel == "width" ? "bottom" : "left";
6589
6731
  this._posArg = this._channel == "x" || this._channel == "width"? args["pathY"] : args["pathX"];
6732
+ this._padding = [ItemType.Line, ItemType.Path].indexOf(items[0].type) >= 0 ? 10 : 0;
6590
6733
 
6591
6734
  this._item = items[0];
6592
6735
  this._items = items;
@@ -6738,22 +6881,24 @@
6738
6881
  let num = this._channel == "x" ? this._mlayout.numRows : this._mlayout.numCols;
6739
6882
  if (this._channel == "x") {
6740
6883
  let left = cb[0].left, numCols = this._mlayout.numCols;
6884
+ let padding = this._orientation === "bottom" ? this._padding : - this._padding;
6741
6885
  for (let r = 0; r < num; r++){
6742
6886
  this._rules.children[r]._setVertices([
6743
- [left, this._posArg ? cb[r * numCols][this._orientation] + this._posArg - cb[0][this._orientation] : cb[r * numCols][this._orientation] ],
6744
- [left + cb[0].width * numCols + this._mlayout.colGap * (numCols - 1), this._posArg ? cb[r * numCols][this._orientation] + this._posArg - cb[0][this._orientation] : cb[r * numCols][this._orientation]]
6887
+ [left, this._posArg ? cb[r * numCols][this._orientation] + this._posArg - cb[0][this._orientation] : cb[r * numCols][this._orientation] + padding],
6888
+ [left + cb[0].width * numCols + this._mlayout.colGap * (numCols - 1), this._posArg ? cb[r * numCols][this._orientation] + this._posArg - cb[0][this._orientation] : cb[r * numCols][this._orientation] + padding]
6745
6889
  ]);
6746
6890
  }
6747
6891
  } else {
6748
6892
  let top = cb[0].top, numRows = this._mlayout.numRows;
6893
+ let padding = this._orientation === "left" ? this._padding : - this._padding;
6749
6894
  for (let c = 0; c < num; c++){
6750
6895
  // this._rules.children[c]._setVertices([
6751
6896
  // [this._posArg ? cb[c * numRows][this._orientation] + this._posArg - cb[0][this._orientation] : cb[c * numRows][this._orientation], top ],
6752
6897
  // [this._posArg ? cb[c * numRows][this._orientation] + this._posArg - cb[0][this._orientation] : cb[c * numRows][this._orientation], top + cb[0].height * numRows + this._mlayout.rowGap * (numRows - 1), ]
6753
6898
  // ]);
6754
6899
  this._rules.children[c]._setVertices([
6755
- [this._posArg ? cb[c * numRows][this._orientation] + this._posArg - cb[0][this._orientation] : this._mlayout.group.bounds[this._orientation], top ],
6756
- [this._posArg ? cb[c * numRows][this._orientation] + this._posArg - cb[0][this._orientation] : this._mlayout.group.bounds[this._orientation], top + cb[0].height * numRows + this._mlayout.rowGap * (numRows - 1)]
6900
+ [this._posArg ? cb[c * numRows][this._orientation] + this._posArg - cb[0][this._orientation] : this._mlayout.group.bounds[this._orientation] - padding, top ],
6901
+ [this._posArg ? cb[c * numRows][this._orientation] + this._posArg - cb[0][this._orientation] : this._mlayout.group.bounds[this._orientation] - padding, top + cb[0].height * numRows + this._mlayout.rowGap * (numRows - 1)]
6757
6902
  ]);
6758
6903
  }
6759
6904
  }
@@ -6780,9 +6925,10 @@
6780
6925
  let cb = this._mlayout.cellBounds;
6781
6926
  if (this._channel == "x") {
6782
6927
  let dir = this._orientation == "bottom" ? 1 : -1;
6928
+ let padding = this._orientation === "bottom" ? this._padding : - this._padding;
6783
6929
  for (let [i, t] of this._ticks.children.entries()) {
6784
6930
  let pos = this._posArg ? cb[i][this._orientation] + this._posArg - cb[0][this._orientation] + this._tickOffset * dir :
6785
- cb[i][this._orientation] + this._tickOffset * dir;
6931
+ cb[i][this._orientation] + this._tickOffset * dir + padding;
6786
6932
  t._setVertices([
6787
6933
  [cb[i].x, pos],
6788
6934
  [cb[i].x, pos + dir * this._tickSize]
@@ -6791,11 +6937,12 @@
6791
6937
  }
6792
6938
  } else if (this._channel == "y"){
6793
6939
  let dir = this._orientation == "left" ? -1 : 1;
6940
+ let padding = this._orientation === "left" ? this._padding : - this._padding;
6794
6941
  for (let [i, t] of this._ticks.children.entries()) {
6795
6942
  // let xPos = this._posArg ? cb[i][this._orientation] + this._posArg - cb[0][this._orientation] + this._tickOffset * dir :
6796
6943
  // cb[i][this._orientation] + this._tickOffset * dir,
6797
6944
  let xPos = this._posArg ? cb[i][this._orientation] + this._posArg - cb[0][this._orientation] + this._tickOffset * dir :
6798
- this._mlayout.group.bounds[this._orientation] + this._tickOffset * dir,
6945
+ this._mlayout.group.bounds[this._orientation] + this._tickOffset * dir - padding,
6799
6946
  yPos = this._tickAnchor == "middle" ? cb[i].y : cb[i][this._tickAnchor];
6800
6947
  t._setVertices([
6801
6948
  [xPos, yPos],
@@ -6813,10 +6960,11 @@
6813
6960
  if (this._channel == "x") {
6814
6961
  let anchor = this._orientation == "bottom" ? ["center", "top"] : ["center", "bottom"],
6815
6962
  offset = this._orientation == "bottom" ? this._labelOffset : -this._labelOffset;
6963
+ let padding = this._orientation === "bottom" ? this._padding : - this._padding;
6816
6964
  for (let [i, l] of this._labels.children.entries()) {
6817
6965
  l.x = cb[i].x;
6818
6966
  l.y = this._posArg ? cb[i][this._orientation] + this._posArg - cb[0][this._orientation] + offset :
6819
- cb[i][this._orientation] + offset;
6967
+ cb[i][this._orientation] + offset + padding;
6820
6968
  l.anchor = anchor;
6821
6969
  if (this._labelRotation){
6822
6970
  l._rotate = [this._labelRotation, l.x, l.y];
@@ -6827,11 +6975,12 @@
6827
6975
  } else if (this._channel == "y"){
6828
6976
  let anchor = this._orientation == "left" ? ["right", "middle"] : ["left", "middle"],
6829
6977
  offset = this._orientation == "left" ? - this._labelOffset : this._labelOffset;
6978
+ let padding = this._orientation === "left" ? this._padding : - this._padding;
6830
6979
  for (let [i, l] of this._labels.children.entries()) {
6831
6980
  // l.x = this._posArg ? cb[i][this._orientation] + this._posArg - cb[0][this._orientation] + offset
6832
6981
  // : cb[i][this._orientation] + offset;
6833
6982
  l.x = this._posArg ? cb[i][this._orientation] + this._posArg - cb[0][this._orientation] + offset
6834
- : this._mlayout.group.bounds[this._orientation] + offset;
6983
+ : this._mlayout.group.bounds[this._orientation] + offset - padding;
6835
6984
  l.y = this._tickAnchor == "middle" ? cb[i].y : cb[i][this._tickAnchor];
6836
6985
  l.anchor = anchor;
6837
6986
  if (this._labelRotation) {
@@ -6871,7 +7020,8 @@
6871
7020
  }
6872
7021
 
6873
7022
  matches(item) {
6874
- return getEncodingKey(this._item).split("_")[0] === getEncodingKey(item).split("_")[0];
7023
+ return sameClass(this._item, item);
7024
+ //return getEncodingKey(this._item).split("_")[0] === getEncodingKey(item).split("_")[0];
6875
7025
  }
6876
7026
  }
6877
7027
 
@@ -6998,17 +7148,18 @@
6998
7148
  for (let i = 0; i < uniqueVals.length; i+= Math.ceil(uniqueVals.length/10))
6999
7149
  stops.push(uniqueVals[i]);
7000
7150
  } else {
7001
- let interval = (domain[1] - domain[0])/9;
7151
+ let incr = (domain[1] - domain[0])/9;
7002
7152
  for (let i = 0; i < 10; i++)
7003
- stops.push(domain[0] + i * interval);
7004
- let decimalPlaces = 0;
7005
- while (interval < 1) {
7006
- interval *= 10;
7007
- decimalPlaces++;
7008
- }
7009
- stops = stops.map(d => d.toFixed(decimalPlaces));
7153
+ stops.push(domain[0] + i * incr);
7010
7154
  }
7011
7155
  }
7156
+ //determine decimal places
7157
+ let decimalPlaces = 0, interval = (stops[stops.length - 1] - stops[0])/stops.length;
7158
+ while (interval < 1) {
7159
+ interval *= 10;
7160
+ decimalPlaces++;
7161
+ }
7162
+ stops = stops.map(d => d.toFixed(decimalPlaces));
7012
7163
  if (this._orientation == Orientation.Vertical) {
7013
7164
  gradient = new LinearGradient({x1: 0, y1: 100, x2: 0, y2: 0});
7014
7165
  stops.forEach(d => {
@@ -9122,7 +9273,7 @@
9122
9273
  }
9123
9274
 
9124
9275
  if (layout)
9125
- scene.setProperties(c.firstChild, {layout: layout});
9276
+ scene.setProperties(c.firstChild, {layout: layout});
9126
9277
  //return results;
9127
9278
  }
9128
9279
 
@@ -10610,12 +10761,18 @@
10610
10761
  .attr("width", d => d.width).attr("height", rowGap)
10611
10762
  .style("fill", "pink").style("opacity", 0.5)
10612
10763
  ;
10613
- let left = Math.min(...cellBounds.map(d => d.left)),
10614
- top = Math.min(...cellBounds.map(d => d.top));
10615
- this._decoMap[gridId].append("rect").attr("x", left).attr("y", top)
10616
- .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)
10617
10767
  .attr("stroke", "blue").attr("stroke-width", "1px")
10618
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");
10619
10776
 
10620
10777
  }
10621
10778
 
@@ -10813,6 +10970,8 @@
10813
10970
 
10814
10971
  if (this._mapping)
10815
10972
  json.mapping = this._mapping;
10973
+ if (this._baseItem)
10974
+ json.baseItem = this._baseItem.id;
10816
10975
  return json;
10817
10976
  }
10818
10977
 
@@ -11824,7 +11983,7 @@
11824
11983
  return scn;
11825
11984
  }
11826
11985
 
11827
- _loadScale(s) {
11986
+ _loadScale(s, scn) {
11828
11987
  let scale;
11829
11988
  // if (s.type === "sequentialColor" && s.scheme) {
11830
11989
  if (s.type.indexOf("Color") > 0 && s.scheme) {
@@ -11842,6 +12001,8 @@
11842
12001
  scale._mapping = s.mapping;
11843
12002
  if ("includeZero" in s)
11844
12003
  scale.includeZero = s.includeZero;
12004
+ if ("baseItem" in s)
12005
+ scale._baseItem = scn.getItem(s.baseItem);
11845
12006
  this.scales[scale.id] = scale;
11846
12007
  //console.log(scale.domain, scale.range);
11847
12008
  }
@@ -24148,9 +24309,13 @@
24148
24309
  }
24149
24310
 
24150
24311
  function canDivide(compnt) {
24151
- if ([ItemType.Line, ItemType.Circle, ItemType.Rect, ItemType.Area, ItemType.Ring, ItemType.Pie].indexOf(compnt.type) < 0) {
24312
+ if ([ItemType.Line, ItemType.Circle, ItemType.Rect, ItemType.Area, ItemType.Ring, ItemType.Pie, ItemType.Path].indexOf(compnt.type) < 0) {
24152
24313
  return false;
24153
24314
  }
24315
+
24316
+ if (compnt.type === ItemType.Path && (compnt.closed || !compnt.firstVertex.dataScope))
24317
+ return false;
24318
+
24154
24319
  if (!compnt.dataScope) {
24155
24320
  return true;
24156
24321
  } else {
@@ -24169,8 +24334,7 @@
24169
24334
  }
24170
24335
  if (!compnt.dataScope) {
24171
24336
  return true;
24172
- }
24173
- else {
24337
+ } else {
24174
24338
  let peers = getPeers(compnt, compnt.getScene());
24175
24339
  for (let p of peers) {
24176
24340
  if (p.dataScope.numTuples > 1)
@@ -24180,6 +24344,14 @@
24180
24344
  }
24181
24345
  }
24182
24346
 
24347
+ function canFormGlyph(args) {
24348
+ for (let itm of args) {
24349
+ if (!isMark(itm))
24350
+ return false;
24351
+ }
24352
+ return true;
24353
+ }
24354
+
24183
24355
  function canClassify(item) {
24184
24356
  if (item.type !== ItemType.Collection) return false;
24185
24357
  if (item.children.length < 2) return false;
@@ -24205,6 +24377,7 @@
24205
24377
  exports.canClassify = canClassify;
24206
24378
  exports.canDensify = canDensify;
24207
24379
  exports.canDivide = canDivide;
24380
+ exports.canFormGlyph = canFormGlyph;
24208
24381
  exports.canRepeat = canRepeat;
24209
24382
  exports.cartesianToPolar = cartesianToPolar;
24210
24383
  exports.createScale = createScale;