@osfarm/itineraire-technique 1.1.6 → 1.1.9

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.
package/README.md CHANGED
@@ -47,7 +47,7 @@ Puis de copier les fichiers js et css dans votre code :
47
47
  Dans votre HTML, inclure les fichiers d'echarts :
48
48
  ```
49
49
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
50
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.js"></script>
50
+ <script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.js"></script>
51
51
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
52
52
  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.7/underscore-umd-min.js"></script>
53
53
 
@@ -1 +1 @@
1
- div.mainITKContainer{container-type:inline-size;container-name:myparent;line-height:1rem}div.mainITKContainer .left-transcript{display:none;width:50%;font-family:Segoe UI}div.mainITKContainer .chart-div{width:100%}div.mainITKContainer .bottom-transcript{display:block;font-family:Segoe UI}@container myparent (min-width: 800px){div.mainITKContainer .left-transcript{display:block}div.mainITKContainer .chart-div{width:50%}div.mainITKContainer .bottom-transcript{display:none}}@container myparent (min-width: 1200px){div.mainITKContainer .left-transcript{display:block;width:40%}div.mainITKContainer .chart-div{width:60%}div.mainITKContainer .bottom-transcript{display:none}}div.mainITKContainer div.rotation_item{background-color:#f8fafc;border-left:#fff 14px solid;border-radius:7px;padding:8px 3px 5px 9px;margin:12px 0;overflow-x:hidden}div.mainITKContainer div.rotation_item.highlighted{box-shadow:0px 4px 4px 0px var(--UI-Shadow, rgba(17, 36, 69, 0.16)),0px 1px 16px 0px var(--UI-Shadow, rgba(17, 36, 69, 0.16));background-color:#f0f3f5}div.mainITKContainer div.rotation_item div.step-header h4{margin-bottom:0;margin-top:0}div.mainITKContainer div.rotation_item div.step-header div.step_dates{font-size:11px;background-color:#cdcccc;color:#fff;margin:5px;padding:2px 5px;border-radius:5px;height:20px;float:right}div.mainITKContainer div.rotation_item div.step-header div.collapse-button{border-radius:50%;width:30px;height:30px;text-align:center;padding:5px 0px;margin:0 5px 3px 0;cursor:pointer;background:#dfe6f7;color:#7a8bad;float:right;transition:transform .3s ease-in-out}div.mainITKContainer div.rotation_item .step_description{clear:both}div.mainITKContainer div.rotation_item.show-all div.collapse-button{transform:rotate(180deg)}div.mainITKContainer div.rotation_item.show-all div.details{max-height:10000px}div.mainITKContainer div.rotation_item div.details{max-height:0px;overflow:hidden;transition:max-height .3s ease-in-out}div.mainITKContainer div.rotation_item div.details div.intervention{background-color:#fff;border-radius:5px;margin-bottom:11px;padding:13px;cursor:pointer}div.mainITKContainer div.rotation_item div.details div.intervention span.intervention_title{font-weight:bold}div.mainITKContainer div.rotation_item div.details div.intervention span.intervention_date{color:#707070;background-color:#f0f3f5;float:right}div.mainITKContainer div.rotation_item div.details div.intervention div.intervention_description{margin-top:5px}div.mainITKContainer div.rotation_item .step-edit{display:none}div.mainITKContainer .charts{width:100%;height:500px;display:inline-block}div.mainITKContainer .transcript{font-size:80%;width:100%;max-height:450px;overflow-y:scroll;scroll-behavior:smooth;padding:3px}.rotation-tooltip{max-width:400px}/*# sourceMappingURL=styles-rendering.css.map */
1
+ div.mainITKContainer{container-type:inline-size;container-name:myparent;line-height:1rem}div.mainITKContainer .left-transcript{display:none;width:50%;font-family:Segoe UI}div.mainITKContainer .chart-div{width:100%}div.mainITKContainer .bottom-transcript{display:none}div.mainITKContainer.withTranscript .bottom-transcript{display:block;font-family:Segoe UI}@container myparent (min-width: 800px){div.mainITKContainer.withTranscript .left-transcript{display:block}div.mainITKContainer.withTranscript .chart-div{width:50%}div.mainITKContainer.withTranscript .bottom-transcript{display:none}}@container myparent (min-width: 1200px){div.mainITKContainer.withTranscript .left-transcript{display:block;width:40%}div.mainITKContainer.withTranscript .chart-div{width:60%}div.mainITKContainer.withTranscript .bottom-transcript{display:none}}div.mainITKContainer div.rotation_item{background-color:#f8fafc;border-left:#fff 14px solid;border-radius:7px;padding:8px 3px 5px 9px;margin:12px 0;overflow-x:hidden}div.mainITKContainer div.rotation_item.highlighted{box-shadow:0px 4px 4px 0px var(--UI-Shadow, rgba(17, 36, 69, 0.16)),0px 1px 16px 0px var(--UI-Shadow, rgba(17, 36, 69, 0.16));background-color:#f0f3f5}div.mainITKContainer div.rotation_item div.step-header h4{margin-bottom:0;margin-top:0}div.mainITKContainer div.rotation_item div.step-header div.step_dates{font-size:11px;background-color:#cdcccc;color:#fff;margin:5px;padding:2px 5px;border-radius:5px;height:20px;float:right}div.mainITKContainer div.rotation_item div.step-header div.collapse-button{border-radius:50%;width:30px;height:30px;text-align:center;padding:5px 0px;margin:0 5px 3px 0;cursor:pointer;background:#dfe6f7;color:#7a8bad;float:right;transition:transform .3s ease-in-out}div.mainITKContainer div.rotation_item .step_description{clear:both}div.mainITKContainer div.rotation_item.show-all div.collapse-button{transform:rotate(180deg)}div.mainITKContainer div.rotation_item.show-all div.details{max-height:10000px}div.mainITKContainer div.rotation_item div.details{max-height:0px;overflow:hidden;transition:max-height .3s ease-in-out}div.mainITKContainer div.rotation_item div.details div.intervention{background-color:#fff;border-radius:5px;margin-bottom:11px;padding:13px;cursor:pointer}div.mainITKContainer div.rotation_item div.details div.intervention span.intervention_title{font-weight:bold}div.mainITKContainer div.rotation_item div.details div.intervention span.intervention_date{color:#707070;background-color:#f0f3f5;float:right}div.mainITKContainer div.rotation_item div.details div.intervention div.intervention_description{margin-top:5px}div.mainITKContainer div.rotation_item .step-edit{display:none}div.mainITKContainer .charts{width:100%;height:500px;display:inline-block}div.mainITKContainer .transcript{font-size:80%;width:100%;max-height:450px;overflow-y:scroll;scroll-behavior:smooth;padding:3px}.rotation-tooltip{max-width:400px}/*# sourceMappingURL=styles-rendering.css.map */
@@ -1 +1 @@
1
- {"version":3,"sourceRoot":"","sources":["../scss/styles-rendering.scss"],"names":[],"mappings":"AAAA,qBACI,2BACA,wBACA,iBAEA,sCACI,aACA,UACA,qBAGJ,gCACI,WAGJ,wCACI,cACA,qBAGJ,uCACI,sCACI,cAGJ,gCACI,UAGJ,wCACI,cAIR,wCACI,sCACI,cACA,UAGJ,gCACI,UAGJ,wCACI,cAIR,uCACI,yBACA,4BACA,kBACA,wBACA,cACA,kBAEA,mDACI,8HAEA,yBAIA,0DACI,gBACA,aAGJ,sEACI,eACA,yBACA,WACA,WACA,gBACA,kBACA,YACA,YAGJ,2EACI,kBACA,WACA,YACA,kBACA,gBACA,mBACA,eACA,mBACA,cACA,YACA,qCAIR,yDACI,WAIA,oEACI,yBAGJ,4DACI,mBAKR,mDACI,eACA,gBACA,sCAEA,oEACI,sBACA,kBACA,mBACA,aACA,eAEA,4FACI,iBAGJ,2FACI,cACA,yBACA,YAGJ,iGACI,eAKZ,kDACI,aAIR,6BACI,WACA,aACA,qBAGJ,iCACI,cACA,WACA,iBACA,kBACA,uBACA,YAIR,kBACI","file":"styles-rendering.css"}
1
+ {"version":3,"sourceRoot":"","sources":["../scss/styles-rendering.scss"],"names":[],"mappings":"AAAA,qBACI,2BACA,wBACA,iBAEA,sCACI,aACA,UACA,qBAGJ,gCACI,WAGJ,wCACI,aAIA,uDACI,cACA,qBAIR,uCAEQ,qDACI,cAGJ,+CACI,UAGJ,uDACI,cAKZ,wCAEQ,qDACI,cACA,UAGJ,+CACI,UAGJ,uDACI,cAKZ,uCACI,yBACA,4BACA,kBACA,wBACA,cACA,kBAEA,mDACI,8HAEA,yBAIA,0DACI,gBACA,aAGJ,sEACI,eACA,yBACA,WACA,WACA,gBACA,kBACA,YACA,YAGJ,2EACI,kBACA,WACA,YACA,kBACA,gBACA,mBACA,eACA,mBACA,cACA,YACA,qCAIR,yDACI,WAIA,oEACI,yBAGJ,4DACI,mBAKR,mDACI,eACA,gBACA,sCAEA,oEACI,sBACA,kBACA,mBACA,aACA,eAEA,4FACI,iBAGJ,2FACI,cACA,yBACA,YAGJ,iGACI,eAKZ,kDACI,aAIR,6BACI,WACA,aACA,qBAGJ,iCACI,cACA,WACA,iBACA,kBACA,uBACA,YAIR,kBACI","file":"styles-rendering.css"}
package/editor.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
7
  <title>Itinéraire technique TIKA</title>
8
8
 
9
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.js"></script>
10
10
 
11
11
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
12
12
  <script src="https://cdn.jsdelivr.net/npm/jquery-ui@1.14.1/dist/jquery-ui.min.js"></script>
package/index.html CHANGED
@@ -58,7 +58,7 @@
58
58
 
59
59
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
60
60
  integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
61
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.js"></script>
61
+ <script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.js"></script>
62
62
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
63
63
  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.7/underscore-umd-min.js"></script>
64
64
 
@@ -240,7 +240,7 @@
240
240
  <div class="code-block mb-4">
241
241
  <pre>
242
242
  &lt;link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"&gt;
243
- &lt;script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.js"&gt;&lt;/script&gt;
243
+ &lt;script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.js"&gt;&lt;/script&gt;
244
244
  &lt;script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"&gt;&lt;/script&gt;
245
245
  &lt;script src="https://cdn.jsdelivr.net/npm/underscore@1.13.7/underscore-umd-min.js"&gt;&lt;/script&gt;
246
246
  <br>
@@ -29,35 +29,45 @@ class RotationRenderer {
29
29
 
30
30
  this.itk_container = $("#" + divID).css({ 'width': '100%' });
31
31
 
32
- if (this.data.options.show_transcript) {
33
- if (this.itk_container.find('.mainITKContainer').length == 0) {
34
- this.itk_container.append(`<div class="row mainITKContainer">
35
- <div class="col-auto left-transcript"><div class="transcript"></div></div>
36
- <div class="col col-auto chart-div"><div class="charts"></div></div>
37
- <div class="col col-12 bottom-transcript"><div class="transcript"></div></div>
38
- </div>`);
39
- }
40
- } else {
41
- if (this.itk_container.find('.mainITKContainer').length == 0) {
42
- this.itk_container.append(`<div class="row mainITKContainer">
43
- <div class="col col-12 chart-div"><div class="charts"></div></div>
44
- </div>`);
45
- }
32
+ if (this.itk_container.find('.mainITKContainer').length == 0) {
33
+ this.itk_container.append(`<div class="row mainITKContainer ` + (this.data.options.show_transcript ? 'withTranscript' : '') + `">
34
+ <div class="col-auto left-transcript"><div class="transcript"></div></div>
35
+ <div class="col col-auto chart-div"><div class="charts"></div></div>
36
+ <div class="col col-12 bottom-transcript"><div class="transcript"></div></div>
37
+ </div>`);
46
38
  }
47
39
  }
48
40
 
49
41
  fixRotationData(rotationData) {
42
+ if (!rotationData || typeof rotationData !== 'object') return rotationData;
43
+
50
44
  if (rotationData.options == undefined)
51
45
  rotationData.options = {};
52
46
 
53
47
  if (rotationData.options?.view == undefined || rotationData?.options?.view == '')
54
48
  rotationData.options.view = 'horizontal';
55
49
 
50
+ // Safety check for steps array
51
+ if (!rotationData.steps || !Array.isArray(rotationData.steps)) {
52
+ rotationData.steps = [];
53
+ return rotationData;
54
+ }
55
+
56
56
  // Map rotationData items to make sure that the startDate and endDate are proper Date objects
57
57
  rotationData.steps.map((item) => {
58
+ if (!item) return item;
59
+
58
60
  item.startDate = new Date(item.startDate);
59
61
  item.endDate = new Date(item.endDate);
60
62
 
63
+ // Safety check for invalid dates
64
+ if (isNaN(item.startDate.getTime())) {
65
+ item.startDate = new Date();
66
+ }
67
+ if (isNaN(item.endDate.getTime()) || item.endDate <= item.startDate) {
68
+ item.endDate = new Date(item.startDate.getTime() + (30 * 24 * 60 * 60 * 1000)); // Default to 30 days later
69
+ }
70
+
61
71
  // Add a duration in months
62
72
  item.duration = Math.round((item.endDate - item.startDate) / (30 * 1000 * 60 * 60 * 24));
63
73
 
@@ -348,9 +358,14 @@ class RotationRenderer {
348
358
 
349
359
  // Avoid words that are too long
350
360
  testWidth = echarts.format.getTextRect(line).width;
351
- while (testWidth > maxWidth) {
361
+ let trimCount = 0;
362
+ const maxTrimCount = line.length; // Prevent infinite loop
363
+
364
+ while (testWidth > maxWidth && line.length > 0 && trimCount < maxTrimCount) {
352
365
  line = line.slice(0, -1);
366
+ if (line.length === 0) break; // Safety check
353
367
  testWidth = echarts.format.getTextRect(line).width;
368
+ trimCount++;
354
369
  }
355
370
  }
356
371
 
@@ -384,101 +399,221 @@ class RotationRenderer {
384
399
  let maxXPositions = new Map();
385
400
 
386
401
  function renderItem(params, api) {
402
+ // Safety checks to prevent crashes
403
+ if (!params || !api) return null;
404
+
405
+ try {
406
+ var categoryIndex = api.value(0);
407
+ var start = api.coord([api.value(1), categoryIndex]);
408
+ var end = api.coord([api.value(2), categoryIndex]);
409
+ var name = api.value(3);
410
+ var type = api.value(4);
411
+ let secondary_crop = api.value(5);
412
+ let bHasSecondaryCrops = api.value(6);
413
+
414
+ // Safety check for invalid coordinates
415
+ if (!start || !end || start.length < 2 || end.length < 2) return null;
416
+ if (isNaN(start[0]) || isNaN(start[1]) || isNaN(end[0]) || isNaN(end[1])) return null;
417
+
418
+ const x = start[0];
419
+ let y = start[1];
420
+
421
+ const style = api.style();
422
+ style.opacity = 0.5;
423
+
424
+ if (params.context.rendered == undefined) {
425
+ // Start of a new rendering round
426
+ maxXPositions = new Map();
427
+
428
+ for (let catIndex = 0; catIndex < 3; catIndex++) {
429
+ for (let track = 0; track < 3; track++) {
430
+ maxXPositions.set('track_left_' + catIndex + '_' + track, params.coordSys.width);
431
+ }
432
+ }
433
+ }
387
434
 
388
- var categoryIndex = api.value(0);
389
- var start = api.coord([api.value(1), categoryIndex]);
390
- var end = api.coord([api.value(2), categoryIndex]);
391
- var name = api.value(3);
392
- var type = api.value(4);
393
- let secondary_crop = api.value(5);
394
- let bHasSecondaryCrops = api.value(6);
435
+ params.context.rendered = true;
395
436
 
396
- const x = start[0];
397
- let y = start[1];
437
+ // Remove the default emphasis style
438
+ api.styleEmphasis({});
398
439
 
399
- const style = api.style();
400
- style.opacity = 0.5;
440
+ if (type == 'rotation_item') {
401
441
 
402
- if (params.context.rendered == undefined) {
403
- // Start of a new rendering round
404
- maxXPositions = new Map();
442
+ // start[0] // abscisse gauche de l'élément (après zoom)
443
+ // start[1] // ordonnée gauche de l'élément
444
+ // end[0] // abscisse droite de l'élément (après zoom)
445
+ // end[1] // ordonnée droite de l'élément
446
+ // height // Hauteur de l'élément
405
447
 
406
- for (let catIndex = 0; catIndex < 3; catIndex++) {
407
- for (let track = 0; track < 3; track++) {
408
- maxXPositions.set('track_left_' + catIndex + '_' + track, params.coordSys.width);
448
+ // params.coordSys.x // début du canva
449
+ // params.coordSys.y // début du canva
450
+ // params.coordSys.width, // largeur du canva
451
+ // params.coordSys.height // hauteur du canva
452
+
453
+ let height = self.barHeight - 20; // 20 px margin top and bottom
454
+ let top = y - height / 2;
455
+ let textXMargin = 2;
456
+ let textYMargin = 10;
457
+
458
+ if (bHasSecondaryCrops) {
459
+ height = self.barHeight - 40; // 20 px margin top and bottom
460
+ top = y - height / 2 - 15;
461
+ textXMargin = 2;
462
+ textYMargin = 10;
409
463
  }
410
- }
411
- }
412
464
 
413
- params.context.rendered = true;
465
+ if (secondary_crop) {
466
+ // Move secondary crops a bit down and reduce their size
467
+ top = top + height + 5;
468
+ height = height / 3;
469
+ textXMargin = 5;
470
+ textYMargin = 5;
471
+ }
414
472
 
415
- // Remove the default emphasis style
416
- api.styleEmphasis({});
473
+ const arrowWidth = height / 3;
474
+ const border = 3;
417
475
 
418
- if (type == 'rotation_item') {
476
+ var points = [
477
+ [x, top],
478
+ [end[0] - border, top],
479
+ [end[0] + arrowWidth - border, top + height / 2],
480
+ [end[0] - border, top + height],
481
+ [x, top + height],
482
+ [x + arrowWidth, top + height / 2],
483
+ ];
419
484
 
420
- // start[0] // abscisse gauche de l'élément (après zoom)
421
- // start[1] // ordonnée gauche de l'élément
422
- // end[0] // abscisse droite de l'élément (après zoom)
423
- // end[1] // ordonnée droite de l'élément
424
- // height // Hauteur de l'élément
485
+ //const itemLabelWidth = echarts.format.getTextRect(name).width + textMargin * 2;
486
+ const itemWidth = end[0] - x;
425
487
 
426
- // params.coordSys.x // début du canva
427
- // params.coordSys.y // début du canva
428
- // params.coordSys.width, // largeur du canva
429
- // params.coordSys.height // hauteur du canva
488
+ // if (itemLabelWidth > itemWidth)
489
+ // name = ''; // Hide the label as we won't have the room to show it
430
490
 
431
- let height = self.barHeight - 20; // 20 px margin top and bottom
432
- let top = y - height / 2;
433
- let textXMargin = 2;
434
- let textYMargin = 10;
491
+ name = wrapText(echarts, name, itemWidth - arrowWidth, height);
435
492
 
436
- if (bHasSecondaryCrops) {
437
- height = self.barHeight - 40; // 20 px margin top and bottom
438
- top = y - height / 2 - 15;
439
- textXMargin = 2;
440
- textYMargin = 10;
441
- }
493
+ // See this for clip regions : https://stackoverflow.com/questions/71735038/setting-border-and-label-in-custom-apache-echarts
494
+ // https://stackoverflow.com/questions/73653691/how-to-draw-a-custom-triangle-in-renderitem-in-apache-echarts
442
495
 
443
- if (secondary_crop) {
444
- // Move secondary crops a bit down and reduce their size
445
- top = top + height + 5;
446
- height = height / 3;
447
- textXMargin = 5;
448
- textYMargin = 5;
496
+ return (
497
+ {
498
+ type: 'polygon',
499
+ transition: ['shape'],
500
+ shape: {
501
+ points: points
502
+ },
503
+ style: style,
504
+ emphasis: {
505
+ style: {
506
+ shadowBlur: 4,
507
+ shadowOffsetX: 1,
508
+ shadowOffsetY: 2,
509
+ shadowColor: 'rgba(0, 0, 0, 0.2)'
510
+ },
511
+ },
512
+ textConfig: {
513
+ position: [arrowWidth + textXMargin, textYMargin]
514
+ },
515
+ textContent: {
516
+ style: {
517
+ text: name,
518
+ fill: '#000',
519
+ width: 80,
520
+ fontWeight: 'bold'
521
+ }
522
+ }
523
+ }
524
+ );
449
525
  }
450
526
 
451
- const arrowWidth = height / 3;
452
- const border = 3;
527
+ if (type == 'intervention_bottom' || type == 'intervention_top') {
453
528
 
454
- var points = [
455
- [x, top],
456
- [end[0] - border, top],
457
- [end[0] + arrowWidth - border, top + height / 2],
458
- [end[0] - border, top + height],
459
- [x, top + height],
460
- [x + arrowWidth, top + height / 2],
461
- ];
529
+ const height = 20;
530
+ const margin = 10;
531
+ const textMargin = 5;
462
532
 
463
- //const itemLabelWidth = echarts.format.getTextRect(name).width + textMargin * 2;
464
- const itemWidth = end[0] - x;
533
+ // Maintain a list of max x for each row. If the max x is further right than the label we try to push,
534
+ // use another row. If for all rows the space is taken, just drop this item
465
535
 
466
- // if (itemLabelWidth > itemWidth)
467
- // name = ''; // Hide the label as we won't have the room to show it
536
+ let trackToUse = null;
537
+ const itemLabelWidth = echarts.format.getTextRect(name).width + textMargin * 2;
468
538
 
469
- name = wrapText(echarts, name, itemWidth - arrowWidth, height);
539
+ for (let track = 0; track < 3; track++) {
540
+ if (!maxXPositions.has('track_right_' + categoryIndex + '_' + track)) {
541
+ // Situation where the track is empty
542
+ trackToUse = track;
543
+ break;
544
+ }
545
+
546
+ let trackLeft = maxXPositions.get('track_left_' + categoryIndex + '_' + track);
547
+ if (trackLeft > (x + itemLabelWidth)) {
548
+ // Situation where the drawing has started right of the current element
549
+ trackToUse = track;
550
+ break;
551
+ }
470
552
 
471
- // See this for clip regions : https://stackoverflow.com/questions/71735038/setting-border-and-label-in-custom-apache-echarts
472
- // https://stackoverflow.com/questions/73653691/how-to-draw-a-custom-triangle-in-renderitem-in-apache-echarts
553
+ let trackRight = maxXPositions.get('track_right_' + categoryIndex + '_' + track);
554
+ if (trackRight < x) {
555
+ // Situation where the last painted element is sufficiently far on the left
556
+ trackToUse = track;
557
+ break;
558
+ }
559
+ }
473
560
 
474
- return (
475
- {
561
+ if (trackToUse == null)
562
+ return null;
563
+
564
+ let currentRight = maxXPositions.get('track_right_' + categoryIndex + '_' + trackToUse);
565
+ if (currentRight == undefined || currentRight < x + itemLabelWidth)
566
+ maxXPositions.set('track_right_' + categoryIndex + '_' + trackToUse, x + itemLabelWidth);
567
+
568
+ let currentLeft = maxXPositions.get('track_left_' + categoryIndex + '_' + trackToUse);
569
+ if (currentLeft > x)
570
+ maxXPositions.set('track_left_' + categoryIndex + '_' + trackToUse, x);
571
+
572
+ // A nicer solution could be to draw large items in a reduced format until there is enough space for
573
+ // drawing them fully. Unfortunately that would require a two pass drawing which does not exist with Echarts ?
574
+
575
+ const arrowWidth = 3;
576
+
577
+ let arrowTop = y + 55;
578
+ let arrowBottom = y - 55;
579
+
580
+ y = margin + y + trackToUse * (height + margin) - (self.barHeight / 2);
581
+
582
+ var points = [];
583
+ var textPosition = [];
584
+
585
+ if (type == 'intervention_top') {
586
+ textPosition = [textMargin, textMargin];
587
+ points = [
588
+ [x, y],
589
+ [x + itemLabelWidth, y],
590
+ [x + itemLabelWidth, y + height],
591
+ [x + arrowWidth, y + height],
592
+ [x, arrowTop]
593
+ ];
594
+ }
595
+ else {
596
+ textPosition = [textMargin, textMargin + y - arrowBottom];
597
+ points = [
598
+ [x, arrowBottom],
599
+ [x + arrowWidth, y],
600
+ [x + itemLabelWidth, y],
601
+ [x + itemLabelWidth, y + height],
602
+ [x, y + height]
603
+ ];
604
+ }
605
+
606
+ return ({
476
607
  type: 'polygon',
477
608
  transition: ['shape'],
478
609
  shape: {
479
610
  points: points
480
611
  },
481
- style: style,
612
+ style: api.style({
613
+ fill: style.fill,
614
+ stroke: style.fill,
615
+ textFill: '#000',
616
+ }),
482
617
  emphasis: {
483
618
  style: {
484
619
  shadowBlur: 4,
@@ -488,130 +623,21 @@ class RotationRenderer {
488
623
  },
489
624
  },
490
625
  textConfig: {
491
- position: [arrowWidth + textXMargin, textYMargin]
626
+ position: textPosition
492
627
  },
493
628
  textContent: {
494
629
  style: {
495
630
  text: name,
496
631
  fill: '#000',
497
- width: 80,
498
- fontWeight: 'bold'
499
632
  }
500
633
  }
501
634
  }
502
- );
503
- }
504
-
505
- if (type == 'intervention_bottom' || type == 'intervention_top') {
506
-
507
- const height = 20;
508
- const margin = 10;
509
- const textMargin = 5;
510
-
511
- // Maintain a list of max x for each row. If the max x is further right than the label we try to push,
512
- // use another row. If for all rows the space is taken, just drop this item
513
-
514
- let trackToUse = null;
515
- const itemLabelWidth = echarts.format.getTextRect(name).width + textMargin * 2;
635
+ );
516
636
 
517
- for (let track = 0; track < 3; track++) {
518
- if (!maxXPositions.has('track_right_' + categoryIndex + '_' + track)) {
519
- // Situation where the track is empty
520
- trackToUse = track;
521
- break;
522
- }
523
-
524
- let trackLeft = maxXPositions.get('track_left_' + categoryIndex + '_' + track);
525
- if (trackLeft > (x + itemLabelWidth)) {
526
- // Situation where the drawing has started right of the current element
527
- trackToUse = track;
528
- break;
529
- }
530
-
531
- let trackRight = maxXPositions.get('track_right_' + categoryIndex + '_' + track);
532
- if (trackRight < x) {
533
- // Situation where the last painted element is sufficiently far on the left
534
- trackToUse = track;
535
- break;
536
- }
537
637
  }
538
-
539
- if (trackToUse == null)
540
- return null;
541
-
542
- let currentRight = maxXPositions.get('track_right_' + categoryIndex + '_' + trackToUse);
543
- if (currentRight == undefined || currentRight < x + itemLabelWidth)
544
- maxXPositions.set('track_right_' + categoryIndex + '_' + trackToUse, x + itemLabelWidth);
545
-
546
- let currentLeft = maxXPositions.get('track_left_' + categoryIndex + '_' + trackToUse);
547
- if (currentLeft > x)
548
- maxXPositions.set('track_left_' + categoryIndex + '_' + trackToUse, x);
549
-
550
- // A nicer solution could be to draw large items in a reduced format until there is enough space for
551
- // drawing them fully. Unfortunately that would require a two pass drawing which does not exist with Echarts ?
552
-
553
- const arrowWidth = 3;
554
-
555
- let arrowTop = y + 55;
556
- let arrowBottom = y - 55;
557
-
558
- y = margin + y + trackToUse * (height + margin) - (self.barHeight / 2);
559
-
560
- var points = [];
561
- var textPosition = [];
562
-
563
- if (type == 'intervention_top') {
564
- textPosition = [textMargin, textMargin];
565
- points = [
566
- [x, y],
567
- [x + itemLabelWidth, y],
568
- [x + itemLabelWidth, y + height],
569
- [x + arrowWidth, y + height],
570
- [x, arrowTop]
571
- ];
572
- }
573
- else {
574
- textPosition = [textMargin, textMargin + y - arrowBottom];
575
- points = [
576
- [x, arrowBottom],
577
- [x + arrowWidth, y],
578
- [x + itemLabelWidth, y],
579
- [x + itemLabelWidth, y + height],
580
- [x, y + height]
581
- ];
582
- }
583
-
584
- return ({
585
- type: 'polygon',
586
- transition: ['shape'],
587
- shape: {
588
- points: points
589
- },
590
- style: api.style({
591
- fill: style.fill,
592
- stroke: style.fill,
593
- textFill: '#000',
594
- }),
595
- emphasis: {
596
- style: {
597
- shadowBlur: 4,
598
- shadowOffsetX: 1,
599
- shadowOffsetY: 2,
600
- shadowColor: 'rgba(0, 0, 0, 0.2)'
601
- },
602
- },
603
- textConfig: {
604
- position: textPosition
605
- },
606
- textContent: {
607
- style: {
608
- text: name,
609
- fill: '#000',
610
- }
611
- }
612
- }
613
- );
614
-
638
+ } catch (error) {
639
+ console.error('Error in renderItem:', error);
640
+ return null;
615
641
  }
616
642
  }
617
643
 
@@ -681,7 +707,7 @@ class RotationRenderer {
681
707
  if (days >= 0)
682
708
  intDate += ' (J+' + (days == 0 ? '0' : days) + ')';
683
709
  else
684
- intDate += ' (J-' + days + ')';
710
+ intDate += ' (J' + days + ')';
685
711
 
686
712
  let title = intervention.name;
687
713
 
@@ -831,8 +857,29 @@ class RotationRenderer {
831
857
  ];
832
858
 
833
859
  let monthsPerYear = new Map();
860
+
861
+ // Safety check to prevent infinite loops
862
+ if (!steps || steps.length === 0) {
863
+ series.push(months);
864
+ return series;
865
+ }
866
+
834
867
  let startMonth = new Date(steps.at(0).startDate.valueOf());
835
- while (startMonth < steps.at(-1).endDate) {
868
+ let endDate = steps.at(-1).endDate;
869
+
870
+ // Safety check for valid dates and reasonable duration (max 20 years)
871
+ if (isNaN(startMonth.getTime()) ||
872
+ isNaN(endDate.getTime()) ||
873
+ startMonth >= endDate ||
874
+ (endDate - startMonth) > (20 * 365 * 24 * 60 * 60 * 1000)) {
875
+ series.push(months);
876
+ return series;
877
+ }
878
+
879
+ let loopCount = 0;
880
+ const maxLoops = 240; // Max 20 years * 12 months
881
+
882
+ while (startMonth < endDate && loopCount < maxLoops) {
836
883
  const monthName = startMonth.toLocaleDateString(undefined, { month: 'short' });
837
884
  const year = startMonth.getFullYear();
838
885
 
@@ -851,6 +898,7 @@ class RotationRenderer {
851
898
 
852
899
  // increment the current month
853
900
  startMonth.setMonth(startMonth.getMonth() + 1);
901
+ loopCount++;
854
902
  }
855
903
 
856
904
  series.push(months);
@@ -936,7 +984,7 @@ class RotationRenderer {
936
984
  if (days >= 0)
937
985
  dateString += ' (J+' + (days == 0 ? '0' : days) + ')';
938
986
  else
939
- dateString += ' (J-' + days + ')';
987
+ dateString += ' (J' + days + ')';
940
988
 
941
989
  return params.marker + params.name + ' - ' + dateString + '<br>' + params.data.description;
942
990
  }
@@ -950,7 +998,15 @@ class RotationRenderer {
950
998
  "borderWidth": 1
951
999
  },
952
1000
  "feature": {
953
- "myTool1": {
1001
+ "myToolShowTranscription": {
1002
+ "show": true,
1003
+ "title": 'Transcription',
1004
+ "icon": 'path://m5.57814,0c-3.07871,0 -5.57814,2.49943 -5.57814,5.57813l0,7.14003c0,3.07871 2.49943,5.57814 5.57814,5.57814l7.14002,0c3.07871,0 5.57814,-2.49943 5.57814,-5.57814l0,-7.14003c0,-3.07871 -2.49943,-5.57813 -5.57814,-5.57813l-7.14002,0zm0,1.33875l7.14002,0c2.3602,0 4.23939,1.87918 4.23939,4.23938l0,7.14003c0,2.3602 -1.87919,4.23939 -4.23939,4.23939l-7.14002,0c-2.3602,0 -4.23939,-1.87919 -4.23939,-4.23939l0,-7.14003c0,-2.3602 1.87919,-4.23938 4.23939,-4.23938l0,0zm-1.33875,3.57c-0.36969,0 -0.66938,0.29968 -0.66938,0.66938c0,0.36967 0.29969,0.66938 0.66938,0.66938l9.81752,0c0.36969,0 0.66938,-0.2997 0.66938,-0.66938c0,-0.3697 -0.29969,-0.66938 -0.66938,-0.66938l-9.81752,0zm0,3.57001c-0.36969,0 -0.66938,0.29969 -0.66938,0.66939c0,0.36967 0.29969,0.66938 0.66938,0.66938l9.81752,0c0.36969,0 0.66938,-0.2997 0.66938,-0.66938c0,-0.3697 -0.29969,-0.66939 -0.66938,-0.66939l-9.81752,0zm0,3.57002c-0.36969,0 -0.66938,0.29968 -0.66938,0.66938c0,0.36967 0.29969,0.66938 0.66938,0.66938l9.81752,0c0.36969,0 0.66938,-0.2997 0.66938,-0.66938c0,-0.3697 -0.29969,-0.66938 -0.66938,-0.66938l-9.81752,0z',
1005
+ onclick: function () {
1006
+ self.toggleTranscription();
1007
+ }
1008
+ },
1009
+ "myToolShowAsDonut": {
954
1010
  "show": true,
955
1011
  "title": 'Rotation',
956
1012
  "icon": 'path://M18.15,12.99c-.06-.07-.14-.13-.23-.17l-.38-.15c.33-.93.51-1.92.51-2.96,0-3-1.49-5.65-3.77-7.26l-3.03,2.72c1.72.79,2.91,2.53,2.91,4.54,0,.54-.09,1.07-.25,1.56l-.44-.17c-.3-.12-.64.03-.76.33-.04.1-.05.21-.03.31l.79,4.82c.06.32.36.53.68.47.1-.02.19-.06.27-.13l3.66-3.1c.25-.21.28-.58.07-.82ZM6.94,5.24c.46-.23.97-.39,1.5-.47l.13.59c.07.32.38.52.69.45.1-.02.2-.07.28-.14l3.59-3.31c.23-.22.24-.59.02-.83-.07-.07-.16-.13-.26-.16L8.3.02c-.31-.09-.63.09-.73.39-.03.09-.03.19-.01.29l.06.27c-1.12.2-2.16.6-3.1,1.17l2.41,3.09ZM5.88,5.96l-2.39-3.06c-.98.82-1.79,1.84-2.34,3.01l3.62,1.44c.29-.53.66-1,1.11-1.39ZM4.17,9.72c0-.41.05-.81.14-1.19l-3.63-1.44c-.18.57-.3,1.16-.35,1.77l3.84,1.1c0-.08,0-.16,0-.24ZM9.17,14.72c-1.59,0-3-.74-3.92-1.9l.42-.38c.24-.22.26-.59.04-.83-.07-.08-.16-.14-.26-.17l-4.66-1.47c-.31-.09-.64.08-.73.39-.03.1-.03.2,0,.3l1.12,4.66c.07.31.39.51.7.43.09-.02.18-.07.25-.13l.23-.21c1.63,1.94,4.07,3.18,6.8,3.18,1.3,0,2.54-.28,3.66-.79l-.8-3.99c-.81.56-1.79.9-2.86.9Z',
@@ -959,7 +1015,7 @@ class RotationRenderer {
959
1015
  self.renderChart();
960
1016
  }
961
1017
  },
962
- "myTool2": {
1018
+ "myToolShowAsFrise": {
963
1019
  "show": true,
964
1020
  "title": 'Frise',
965
1021
  "icon": 'path://M4.63,0H0v10.01h4.97c.07,0,.13-.05.19-.14l2.61-4.14c.17-.28.23-.89.12-1.35-.03-.15-.08-.27-.13-.35L5.16.12c-.05-.08-.11-.12-.17-.12h-.37ZM11.9,4.38c-.03-.15-.08-.27-.13-.35L9.17.12c-.05-.08-.11-.12-.17-.12h-.37s-2.26,0-2.26,0v.03s.06.05.08.09l2.6,3.9c.06.08.1.21.13.35.1.47.05,1.07-.12,1.35l-2.61,4.14s-.05.07-.08.09v.04h2.6c.07,0,.13-.05.19-.14l2.61-4.14c.17-.28.23-.89.12-1.35ZM18.28,4.38c-.03-.15-.08-.27-.13-.35L15.55.12c-.05-.08-.11-.12-.17-.12h-.37s-4.63,0-4.63,0v.03s.06.05.08.09l2.6,3.9c.06.08.1.21.13.35.1.47.05,1.07-.12,1.35l-2.61,4.14s-.05.07-.08.09v.04h4.97c.07,0,.13-.05.19-.14l2.61-4.14c.17-.28.23-.89.12-1.35Z',
@@ -997,6 +1053,11 @@ class RotationRenderer {
997
1053
 
998
1054
  return anneeDiff * 12 + moisDiff;
999
1055
  }
1056
+
1057
+ toggleTranscription() {
1058
+ this.itk_container.find('.mainITKContainer').toggleClass('withTranscript');
1059
+ this.chart.resize();
1060
+ }
1000
1061
  }
1001
1062
 
1002
1063
  window.RotationRenderer = RotationRenderer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osfarm/itineraire-technique",
3
- "version": "1.1.6",
3
+ "version": "1.1.9",
4
4
  "description": "A visualisation tool to show agricultural technical itineraries based on Echarts",
5
5
  "main": "editor.html",
6
6
  "scripts": {
@@ -10,7 +10,7 @@
10
10
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
11
11
  integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
12
12
 
13
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.js"></script>
14
14
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
15
15
  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.7/underscore-umd-min.js"></script>
16
16
 
@@ -14,36 +14,46 @@ div.mainITKContainer {
14
14
  }
15
15
 
16
16
  .bottom-transcript {
17
- display : block;
18
- font-family: Segoe UI;
17
+ display: none;
19
18
  }
20
19
 
21
- @container myparent (min-width: 800px) {
22
- .left-transcript {
23
- display: block;
20
+ &.withTranscript {
21
+ .bottom-transcript {
22
+ display : block;
23
+ font-family: Segoe UI;
24
24
  }
25
+ }
25
26
 
26
- .chart-div {
27
- width: 50%;
28
- }
27
+ @container myparent (min-width: 800px) {
28
+ &.withTranscript {
29
+ .left-transcript {
30
+ display: block;
31
+ }
29
32
 
30
- .bottom-transcript {
31
- display: none;
33
+ .chart-div {
34
+ width: 50%;
35
+ }
36
+
37
+ .bottom-transcript {
38
+ display: none;
39
+ }
32
40
  }
33
41
  }
34
42
 
35
43
  @container myparent (min-width: 1200px) {
36
- .left-transcript {
37
- display: block;
38
- width : 40%;
39
- }
44
+ &.withTranscript {
45
+ .left-transcript {
46
+ display: block;
47
+ width : 40%;
48
+ }
40
49
 
41
- .chart-div {
42
- width: 60%;
43
- }
50
+ .chart-div {
51
+ width: 60%;
52
+ }
44
53
 
45
- .bottom-transcript {
46
- display: none;
54
+ .bottom-transcript {
55
+ display: none;
56
+ }
47
57
  }
48
58
  }
49
59
 
@@ -10,7 +10,7 @@
10
10
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
11
11
  integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
12
12
 
13
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.js"></script>
14
14
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
15
15
  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.7/underscore-umd-min.js"></script>
16
16
 
@@ -10,7 +10,7 @@
10
10
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
11
11
  integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
12
12
 
13
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.js"></script>
14
14
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
15
15
  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.7/underscore-umd-min.js"></script>
16
16
  <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">