Ajout d'une légende dans le modeler

Request description

Bonjour,

Quelle serait selon vous la meilleure méthode pour ajouter une légende à un Model ?
J’adapte la couleur des nodes à la valeur d’un champ et je voudrais indiquer à quoi chaque couleur correspond.
J’hésitais à réutiliser le Node titre mais ce n’est pas très propre.
Je devrais peut-être le code dans le script du template ?

Merci pour vos conseils !
Emmanuelle

Steps to reproduce

This request concerns an up-to-date Simplicité instance
and these are the steps to reproduce it:

Technical information

Instance /health
---paste the content of your-instance.com/health---
Simplicité logs
---paste the content of the **relevant** server-side logs---
Browser logs
---paste content of the **relevant** browser-side logs---
Other relevant information

----E.g. type of deployment, browser vendor and version, etc.----

Bonjour,
Le titre n’a pas de hook front.

il faut donc l’ajouter au DOM SVG sur le “ready” qui par défaut ne fait rien sauf appeler la callback quand c’est prêt.

ready: (desktop, cbk) => cbk()

Attention de ne pas utiliser le constructeur jQuery (html), mais qui reste néanmoins utilisable une fois créé dans la bon namespace. Exemple :

let t = document.createElementNS('http://www.w3.org/2000/svg', 'text');
$(t).attr({ x:10, y:20, dy:'1.2rem' })
    .text('My caption')
    .appendTo($('.caption', ctn));

Bonjour,

Je ne m’en sors pas …
J’essaie de créer un élément Légende qui pourra être déplacé au même titre que les Nodes, et sauvegardé dans le PNG.
J’ai créé un Node spécifique pour ça, mais je n’arrive pas à insérer d’image à l’intérieur, ni formater plusieurs lignes de texte.

Pouvez-vous m’aider ?

Merci !

Sans le code spécifique impossible d’aider.
Ma recommandation est de créer/modifier le groupe .caption du modèle standard comme décrit plus haut.

Le reste c’est du SVG et de la manipulation du DOM :

html - How to display multiple lines of text in SVG? - Stack Overflow.
html - How can I display an image inside SVG circle in html5? - Stack Overflow

Exemple (non testé)

// create a caption if not exists
let c = desktop.getCaption();
if (!c.length) {
   desktop.addCaption({});
   c = desktop.getCaption();
}

// replace content
c.empty();
let t1 = document.createElementNS('http://www.w3.org/2000/svg', 'text');
$(t1).attr({ x:5, y:5, dy:'1.2rem' }).text('My caption 1').appendTo(c);
let t2 = document.createElementNS('http://www.w3.org/2000/svg', 'text');
$(t2).attr({ x:5, y:5, dy:'2.4rem' }).text('My caption 2').appendTo(c);
let im = document.createElementNS('http://www.w3.org/2000/svg', 'image');
let url = "https://upload.wikimedia.org/wikipedia/commons/b/b2/WhiteCat.jpg";
$(im).attr({ x:5, y:50, width:100, height:80, "xlink:href": url }).appendTo(c);

// Add a border around content
let rect = c[0].getBBox();
let r = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
$(r).addClass("border").attr({ x:0, y:0, width:rect.width+10, height:rect.height+10 }).appendTo(c);

// Related object with methods move/bind/size/draw...
let caption = c.data("d");
caption.move(100,100);

J’ai l’impression que le model.info est réappelé dans le callback.
Quoi que je fasse, mon élément Caption est décustomisé et remplacé avec les infos du modèle.
Si je supprime la classe “Caption”, j’ai bien ce que je veux mais mon élément ne peut plus être déplacé à la souris …

ready: function(desktop, cbk) { 
	console.log("ready");

	
	

	var item = {x : 60, y: 60, mod_name: "Legend"};
	
	c = new Simplicite.Diagram.Caption(desktop,item);
                
    desktop.containers.append(c.elt);
        c.draw();
        c.elt.removeClass();
		c.elt.addClass("node");
	
	let url = "https://ear-dev.k8s-stage.grouperci.com/resource?type=ICO&code=icon";
	c.create("image", {
            x: 5,
            y: 0,
            dy: "1.2em",
            "xlink:href": url
        }).addClass("icon").appendTo(c.elt);
	

	cbk(); 
	
},

Ok, il y a peut être un refresh après le ready qui resynchronise le modèle pour le mettre à jour avec les données en base. Il faudrait un hook onDrawCaption pour le surcharger proprement.

En attendant, utiliser la classe “node” c’est dangereux car ça correspond à un objet métier et risque d’être utilisé à tord ailleurs.

  • Pour le déplacement il faut binder les évènements souris sur un élément avec une dimension (rect, circle… mais pas un g), par exemple en ajoutant une bordure qui implémente les handlers

  • Attention les images via href sont chargée en asynchrone donc la taille n’est pas connue, il faut donc ajouter un width + height fixe dans l’image sinon getBBox n’aura pas la bonne taille.

  • La methode bind d’un Node fait plus de chose : double click, click droit/context menu…
    Le Caption ne fait que du déplacement, si tu veux du double click, ajoute les au border.

  • Toujours mettre un calback dans un finally, sinon en cas de bug ça risque de ne plus terminer correctement le chargement UI

ready: function(desktop, cbk) { 
  try {
	console.log("ready");
	var item = {x : 60, y: 60, mod_name: "Legend"};	
	let c = new Simplicite.Diagram.Caption(desktop,item);
	desktop.containers.append(c.elt);
	c.draw();
	c.elt.removeClass().addClass("myLegend");
	
	let url = "https://ear-dev.k8s-stage.grouperci.com/resource?type=ICO&code=icon";
	c.create("image", {
            x: 0, y: 0,
            width: 100, height:100,
            "xlink:href": url
        }).addClass("icon").appendTo(c.elt);
	
	// bind a rect to move the caption
	let bound = c.elt.getBBox();
	let b = c.border = c.create("rect", { x:0, y:0, width:bound.width, height:bound.height });
	b.addClass("border").css({ fill:"blue", stroke:"black" }).appendTo(c.elt);

	c.bind(b); // bind mouse/touch events to border
	c.border.click(e => {});
	c.border.dblclick(e => {});
	c.border.contextmenu(e => {});
  }
  finally {
	// Done
	cbk && cbk(); 	
  }
}

Bienvenue dans le SVG et ses subtilités.

1 Like

Après de multiples essais je désespère un peu :sweat_smile:
Je n’arrive pas à manipuler des captions car j’ai l’impression que c’est prévu pour être un élément unique.
Je me suis donc tournée vers les Notes, mais si j’ai bien l’image à l’ouverture du modèle, elle disparaît quelques secondes après.
Help :no_mouth:

ready: function(desktop, cbk) { 
	console.log("ready");

	
	

	var item = {x : 60, y: 60, mod_name: "Legend"};
	
	/*c = new Simplicite.Diagram.Caption(desktop,item);
	c = new Simplicite.Diagram.Element();

    desktop.containers.append(c.elt);
        c.draw();
        c.elt.removeClass().addClass("legend");*/
        
    let c = new Simplicite.Diagram.Note(desktop,{
            x: 50,
            y: 50,
            text: "Legend",
            w: 150,
            h: 150
        });
        desktop.nodes.append(c.elt);
        c.draw();

	let url = "/resource?type=IMG&code=ECB_DIVISION_LEGEND";
	c.create("image", {
            x: 5,
            y: 0,
            width: 100, 
            height:100,
            "xlink:href": url
        }).addClass("image").appendTo(c.elt);
	

	

	cbk(); 
	
},

A l’ouverture

Quelques secondes après

Pourtant je vois bien l’image dans les éléments.

Quand on ouvre ca fait un refresh, donc c’est la méthode draw qu’il te faut surcharger pour qu’ele redessine l’image à chaque “refresh” automatique ou demandé par le user pour resynchroniser.

et il manque à ton algo un test si la légende existe déjà, exemple avec une classe myLegend :

ready: function(desktop, cbk) { 
...
let c, note = desktop.nodes.find(".myLegend");

if (note.length) {
  // Existe déjà
  c = note.data("d");
}
else {
   // Nouvelle legende
    c = new Simplicite.Diagram.Note(desktop,{
            x: 50,
            y: 50,
            text: "Legend",
            w: 150,
            h: 150
        });
    // identifée par une classe dédiée
    desktop.nodes.append(c.elt.addClass("myLegend"));
}

// Surcharge le draw
let defaultDraw = c.draw;
c.draw = function() {
  defaultDraw();
  let url = "/resource?type=IMG&code=ECB_DIVISION_LEGEND";
  c.create("image", { x:5, y:0, width:100, height:100, "xlink:href":url})
      .addClass("image").appendTo(c.elt);
}

Ensuite il y peut être un pb CSS, hidden sur l’image, mauvaise URL…

1 Like

[EDIT] Il manquait xmlns:xlink="http://www.w3.org/1999/xlink" dans le tag SVG. Ca ne règle pas mon souci d’image qui disparaît mais ça explique l’erreur

On dirait que ça plante à la génération du png du Modèle.

Error unable to generate image from src = data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" class="diagram" data-model="613" width="182" height="200"><style>svg{font-family:"Helvetica Neue",Helvetica,sans-serif;font-size:12px;background-color:%23fff}svg .note .border{fill:%23f9ed5a;stroke:%23b5af62}svg .cont .border,svg .node .border,svg .caption .border{fill:%23fff;stroke:%23000}svg .cont .sep,svg .node .sep,svg .caption .sep{stroke:%23000}svg .cont .head{stroke:none;fill:none;fill-opacity:.5}svg .cont .border{fill-opacity:.2}svg .cont text{font-weight:bold}svg .node .item rect{fill:none;stroke:none}svg .node .item text{dominant-baseline:text-after-edge;alignment-baseline:after-edge;fill:%23000}svg .node .item.title rect{fill-opacity:.3}svg .node .item.title text{font-weight:bold}svg .node .item.empty text{text-anchor:middle}svg .caption text.title{font-weight:bold}svg .resize{fill:none;stroke:transparent;stroke-opacity:0;stroke-width:5px}svg .shape{fill:%23fff;stroke:%23000}svg .shape-title{font-weight:bold;fill:%23000;dominant-baseline:text-after-edge;alignment-baseline:after-edge;text-anchor:middle}svg .link .border{fill:none;stroke:transparent;stroke-opacity:0;stroke-width:10px}svg .link .road{fill:none;stroke:%23000;stroke-width:1.5;stroke-linejoin:round;stroke-linecap:round}svg .link .road.dashed{stroke-dasharray:5}svg .link .link-label{text-anchor:middle}</style><defs><filter id="shadow" height="130%"><feGaussianBlur in="SourceAlpha" stdDeviation="3"></feGaussianBlur><feOffset dx="2" dy="2" result="offsetblur"></feOffset><feComponentTransfer><feFuncA type="linear" slope="0.5"></feFuncA></feComponentTransfer><feMerge><feMergeNode></feMergeNode><feMergeNode in="SourceGraphic"></feMergeNode></feMerge></filter><marker id="start_1" markerWidth="12" markerHeight="12" refX="2" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M10,2 L2,6 L10,10" fill="none" stroke="%23000"></path></marker><marker id="end_1" markerWidth="12" markerHeight="12" refX="11" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M2,2 L10,6 L2,10" fill="none" stroke="%23000"></path></marker><marker id="start_2" markerWidth="12" markerHeight="12" refX="2" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M10,2 L2,6 L10,10z" fill="%23fff" stroke="%23000"></path></marker><marker id="end_2" markerWidth="12" markerHeight="12" refX="11" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M2,2 L10,6 L2,10z" fill="%23fff" stroke="%23000"></path></marker><marker id="start_3" markerWidth="22" markerHeight="12" refX="2" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M10,2 L2,6 L10,10 L18,6z" fill="%23000" stroke="%23000"></path></marker><marker id="end_3" markerWidth="22" markerHeight="12" refX="19" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M10,2 L2,6 L10,10 L18,6z" fill="%23000" stroke="%23000"></path></marker><marker id="start_4" markerWidth="22" markerHeight="12" refX="2" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M10,2 L2,6 L10,10 L18,6z" fill="%23fff" stroke="%23000"></path></marker><marker id="end_4" markerWidth="22" markerHeight="12" refX="19" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M10,2 L2,6 L10,10 L18,6z" fill="%23fff" stroke="%23000"></path></marker><marker id="start_6" markerWidth="16" markerHeight="16" refX="2" refY="8" orient="auto" markerUnits="strokeWidth"><path d="M0,0 L16,8 L0,16" fill="none" stroke="%23000"></path></marker><marker id="end_6" markerWidth="12" markerHeight="16" refX="15" refY="8" orient="auto" markerUnits="strokeWidth"><path d="M16,0 L0,8 L16,16" fill="none" stroke="%23000"></path></marker></defs><g class="viewport" data-grid="0" transform="matrix(1 0 0 1 0 0)" data-zoom="1" data-x="0" data-y="0"><g class="background"></g><g class="body"><g class="containers"></g><g class="links"></g><g class="nodes"><g data-x="20" data-y="20" transform="translate(20 20)" data-color="%23EA9999" class="node" data-obj="RciApplication" data-id="1587" data-tpl="FloApp_app_send" data-keys="{&quot;rciAppTechnicalId&quot;:&quot;APP-1587t&quot;,&quot;rciAppVersion&quot;:1}" data-w="142.0221405029297" data-h="43.33333206176758"><rect x="0" y="0" width="142.0221405029297" height="43.33333206176758" rx="5" ry="5" class="border" filter="url(%23shadow)"></rect><g class="content" data-obj="RciApplication"><g class="item title empty" data-id="1587"><rect x="1" y="0" width="140.0221405029297" height="43.33333206176758" class="border-item" rx="5" ry="5" style="fill: rgb(232, 116, 36);"></rect><text x="71.01107025146484" y="21.66666603088379" dy="0.5em">AGREE 21 (RCI DE) </text></g></g></g><g data-x="50" data-y="50" transform="translate(50 50)" class="note legend" data-id="1" data-w="48.052085876464844" data-h="21.33333396911621"><rect x="0" y="0" width="48.052085876464844" height="21.33333396911621" class="border" style="fill: rgb(249, 237, 90);"></rect><text y="0"><tspan x="4" dy="1.2em" data-eol="1">Legend</tspan></text><rect x="0" y="0" width="48.052085876464844" height="21.33333396911621" class="resize"></rect><image x="0" y="30" width="100" height="100" xlink:href="/resource?type=IMG&amp;code=ECB_DIVISION_LEGEND" class="image"></image></g></g></g><g class="foreground"></g></g></svg>

Je tombe dans le onerror mais je n’arrive pas à débugger parce que tout ce qu’il fait juste avant c’est un new Image() …

this.img2base64 = function(src, format, w, h, cbk) {
        let im = new Image();
        im.onload = ()=>{
            w = Math.round(w === null ? im.width : (w === 0 && im.height ? im.width * h / im.height : w));
            h = Math.round(h === null ? im.height : (h === 0 && im.width ? im.height * w / im.width : h));
            let c = document.createElement("canvas"), ctx = c.getContext("2d"), data;
            c.width = w;
            c.height = h;
            ctx.drawImage(im, 0, 0, w, h);
            data = c.toDataURL("image/" + (format || "png"));
            $(im).remove();
            c = null;
            cbk && cbk(data, w, h);
        }
        ;
        im.onerror = ()=>{
            console.log("Error unable to generate image from src = " + src);
            cbk && cbk("", 0, 0);
        }

L’image ou le canvas HTML ne doit pas savoir charger une URL externe href = il faut mettre ton image en base64 dans la partie <defs> du SVG et te servir de son id dans le href dans l’image.

comme les icones d’objet, utilise la methode du desktop qui te retourne une image en la sérialisant dans le SVG :

// Image size
let name = "mylegend", w=100, h=200;
// replace older def
desktop.removeDefImage(name, w, h);
// add url image to defs
desktop.addDefImage(name, url, w, h, "image", im => {
  // append a use element
   c.elt.append(desktop.getDefImage(name, w, h));
});

Ca fera ça :

/**
 * Remove image definition
 * @function
 */
this.removeDefImage = function(name, w, h) {
	let id = name+"_"+w+"_"+h;
	$("> defs > #"+id, svg).remove();
};

/**
 * Add base64 image to defs: inlined image for standalone usage (export to image, svg+xml...)
 * @function
 */
this.addDefImage = function(name, url, w, h, cls, cbk) {
	let id = name+"_"+w+"_"+h;
	self.img2base64(url, "png", w, h, (data, w, h) => {
		let im = elt("image", { id:id, width:w, height:h, href:data }).addClass(cls);
		self.addDef(im, true);
		cbk && cbk(im);
	});
};

/**
 * Get defined image
 * @function
 */
this.getDefImage = function(name, w, h) {
	let id = name+"_"+w+"_"+h,
		img = $("> defs > #"+id, svg);
	return elt("use", { href:"#"+id, width: img.attr("width") || w, height: img.attr("height") || h });
};

tu passes au niveau 2 :wink:

ERRATUM : il ne faut pas insérer l’image sérialisée mais un élément <use> via getDefImage.
je ne fais pas du SVG tous les jours… j’ai corrigé l’exemple ci-dessus.

Le besoin de pouvoir étendre/changer de légende (caption) par défaut va être amélioré dans la prochaine release. Le hook onDrawCaption est ajouté pour en changer tout ou partie.

Exemple qui permet de le remplacer par une image :

onDrawCaption: function(caption, display) {
	// build first the default caption with border and texts
	display();
	let elt = caption.elt,
		desktop = caption.desktop,
		name = "mylegend0",
		url = "/r?c=MYLEGEND", // tiny url to image resource
		w = 100, h = 100; // image size

	// Remove all contents except the border
	$(">*",elt).not(".border").remove();
	// Resize the draggable <rect class="border"> to fit the image
	$(".border",elt)
	.attr({
		width: w,
		height: h
	}).css({
		fill: "transparent",  // default is white
		stroke: "transparent" // default is black
	});

	// Replace older image def in <defs> section
	desktop.removeDefImage(name, w, h);
	// Add base64 image to <defs>
	desktop.addDefImage(name, url, w, h, "image", () => {
		// Append a <use> element to def image
		elt.append(desktop.getDefImage(name, w, h));
	});
},

image

L’image est déclarée comme une Ressource code=MYLEGEND de type image dans la disposition default pour être accessible par URL. Avec une URL d’image externe, ça ne fonctionnera pas pour des pb de CORS/cross domain polic, à revoir au besoin mais c’est un autre sujet.

Ah merci beaucoup parce que je ne m’en sors pas avec les notes ! Il y a des traitements qui doivent passer derrière et supprimer mon paramétrage, mais soit l’image disparaît, soit c’est le mousedown :sweat_smile:

J’attends la 5.3.8 alors :slight_smile:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.