« Module:Flowchart » : différence entre les versions

De Wikimedica
Aucun résumé des modifications
(Mise en commentaire car code non fonctionnel)
Ligne 236 : Ligne 236 :
--- Voir https://github.com/mermaid-js/mermaid/issues/637 pour la technique
--- Voir https://github.com/mermaid-js/mermaid/issues/637 pour la technique
--- qui consiste en rajouter des noeuds invisibles.
--- qui consiste en rajouter des noeuds invisibles.
local level
local level = nil
if to == nil then level = nil
--- if to == nil then level = nil
else level = nodes.to.level end
--- else level = nodes.to.level end
if not level == '-' or not level == nil then
if not level == '-' or not level == nil then

Version du 30 juillet 2020 à 00:21


Utilisation

Page principale: Aide:Diagramme

Permet d'afficher un digramme de type "Flowchart" (algorithme). L'utilisation de ce modèle est différente car il ne possède pas de nombre de paramètres définis et les paramètres sont nommés selon la fonction qu'ils occupent dans le flowchart. Seuls les paramètres débutant par $ sont fixes.

Il est conseillé de passer le wikicode pour faire l'édition de ce modèle. L'éditeur visuel n'étant pas adapté à ce cas d'utilisation.

Nœuds

Sous la forme id nœud=Étiquette à afficher pour le nœud, quasiment tous les caractères sont autorisés dans l’identifiant du nœud, sauf ceux utilisés pour déclarer un lien, un paramètre général ou un groupe. On peut en plus définir des propriétés pour les nœuds, telles que :

  • id nœud.shape=... spécifie la forme du nœud, parmi rounded (par défaut), rectangle, circle, flag, diamond.
  • id nœud.group=... définit le groupe de nœuds auquel appartient ce nœud, par défaut aucun.
  • id nœud.style.fill=... couleur de remplissage du nœud, par défaut c’est celle du thème Mermaid actif.
  • id nœud.style.stroke=... idem pour la bordure du nœud ( exemple: red,stroke-width:4px,stroke-dasharray: 5 5)

Liens

Ajout d’un lien entre deux nœuds. Sous la forme id nœud d’origine -> id nœud d’arrivée, ou id nœud d’origine -> id nœud d’arrivée=Étiquette du lien pour ajouter une étiquette. Comme pour les définitions de nœuds, on peut définir des propriétés pour le lien, telles que :

  • id nœud d’origine -> id nœud d’arrivée.shape=... spécifie la forme du lien, parmi curve (par défaut), linear, step, step before, step after.
  • id nœud d’origine -> id nœud d’arrivée.ending=... peut valoir arrow pour ajouter une flèche au bout du lien (par défaut), ou plain pour ne rien y mettre.

Groupes

Définition d’un groupe de nœuds. Les nœuds sont ajoutés à un groupe en affectant leur propriété .group=id du groupe. On peut en plus spécifier l’étiquette du groupe avec la syntaxe group id du groupe=Étiquette du groupe (préfixé de group).

Exemple

{{Flowchart
| $orientation = to right
| Start = Boîte de début
| Start -> A
| A = Lien vers une [[AAA|maladie]]
| A -> End
| End.group = groupe
| group groupe = Un groupe
| Start -> B
| B = Texte en '''gras'''<br>Avec une autre ligne.<br> Et une autre.
| B -> B2
| B2 = Une condition?
| B2.shape = diamond
| B2.style.fill = #ff6666
| B2 -> B3 = Oui
| B3.group = groupe
| B2 -> End = Non
| Start -> C
| C -> Fin 2
| Fin 2.level = 2
| Fin 2.shape = rectangle
| Fin 2.style.fill = #ff9900
}}

Paramètres

Génère un diagramme de type Flowchart (algorithme)

[Modifier les données du modèle]

Paramètres du modèle

La mise en forme multiligne est préférée pour ce modèle.

ParamètreDescriptionTypeÉtat
titre$titre

Titre du graphique

Contenufacultatif
Orientation$orientation

Orientation du flowchart, peut être to bottom, to right, to top, to left.

Par défaut
to bottom
Chaînefacultatif
Largeur$largeur

Force une largeur en pixels. Utile pour agrandir les flowcharts imposants.

Exemple
1200
Nombrefacultatif
Déboggage$debug

Active le mode déboggage

Par défaut
0
Booléenfacultatif

Notes techniques

  • En raison d'un conflit avec l'extension Flow, l'affichage des Flowcharts dans les discussions ne fonctionne pas car la librairie mermaid n'est pas chargée. Un patch a été ajouté dans MediaWiki:Common.js pour pallier à ce problème. Un bogue a été soumis auprès des développeurs.
  • De base, Lua ne parse pas de Wikicode à l'interne, une fonction de parsing a donc été rajoutée à LocalSettings.js

local p = {}

--[======[ Fonctions utilitaires ]======]--

local getArgs = require("Module:Arguments").getArgs

--- Récupère la liste des clés d’un tableau associatif
local function get_table_keys(t)
	local keys = {}
	
	for key, _ in pairs(t) do
		table.insert(keys, '"' .. key .. '"')
	end
	
	return table.concat(keys, ", ")
end

--- Convertit un identifiant d’objet en identifiant utilisé en syntaxe Mermaid
local function generate_id(id)
	-- On utilise une version hachée de l’identifiant de chaque objet (avec un
	-- algorithme de hachage faible, acceptable ici car nous n’en faisons pas
	-- une utilisation cryptographique) afin de permettre l’utilisation de
	-- caractères arbitraires dans le Wikicode sans poser de problèmes au
	-- parseur Mermaid
	return mw.hash.hashValue("md5", id)
end

--- Échappe les guillemets droits utilisés dans une étiquette
local function escape_quotes(label)
	return string.gsub(label, '"', "#quot;")
end

--- Découpe une chaîne autour de la première occurrence d’un délimiteur
local function split_delimiter(s, delim)
	local d_start, d_end = string.find(s, delim, 1, true)
	
	if d_start and d_end then
		local left = mw.text.trim(string.sub(s, 1, d_start - 1))
		local right = mw.text.trim(string.sub(s, d_end + 1))
		return left, right
	else
		return s
	end
end

--- Affecte une valeur dans une clé d’une table, en utilisant un chemin en
--- notation pointée
local function set_key_path(t, key, default_key, value)
	local left, right = split_delimiter(key, ".")
	
	if not right and default_key then
		right = default_key
	end
	
	if not right then
		t[left] = value
	else
		if not t[left] then
			t[left] = {}
		end
		
		set_key_path(t[left], right, nil, value)
	end
end

--[======[ Représentation intermédiaire du flowchart ]======]--

local link_separator = "->"
local group_prefix = "group "

--- Lit la définition d’un flowchart
function p.read_flowchart(args)
	local nodes = {}
	local groups = {}
	local links = {}
	
	for key, value in pairs(args) do
		if type(key) == "number" then
			key = value
			value = nil
		end
		
		local is_group = string.find(key, group_prefix, 1, true) == 1
		local is_link = string.find(key, link_separator, 1, true)
		
		if is_group then
			-- Concerne un groupe de nœuds
			local group_key = string.sub(key, #group_prefix + 1)
			
			set_key_path(groups, group_key, "name", value)
		elseif is_link then
			-- Concerne un lien
			local base, rest = split_delimiter(key, ".")
			local from, to = split_delimiter(base, link_separator)
			local link_key = nil
			
			if not rest then
				link_key = string.format("%s->%s", from, to)
			else
				link_key = string.format("%s->%s.%s", from, to, rest)
			end
			
			if not nodes[from] then
				nodes[from] = {}
			end
			
			if not nodes[to] then
				nodes[to] = {}
			end
			
			set_key_path(links, link_key, "name", value)
		else
			-- Concerne un nœud
			set_key_path(nodes, key, "name", value)
		end
	end
	
	return nodes, groups, links
end

--[======[ Génération de code Mermaid ]======]--

local orientations = {
	["to bottom"] = "TB",
	["to top"] = "BT",
	["to right"] = "LR",
	["to left"] = "RL",
}

local nodes_shapes = {
	rectangle = '%s["%s"]',
	rounded = '%s("%s")',
	circle = '%s(("%s"))',
	flag = '%s>"%s"]',
	diamond = '%s{"%s"}',
}

local endings = {
	plain = "---",
	arrow = "-->",
}

local links_shapes = {
	curve = "basis",
	linear = "linear",
	["step before"] = "stepBefore",
	["step after"] = "stepAfter",
	step = "step",
}

--- Génère le code Mermaid pour un flowchart
function p.flowchart_to_mermaid(params, nodes, groups, links)
	local lines = {}
	local orientation = params.orientation or "to bottom"
	
	if not orientations[orientation] then
		error(string.format(
			'Orientation "%s" inconnue pour le flowchart. Les orientations possibles sont %s.',
			orientation, get_table_keys(orientations)
		), 0)
	end
	
	table.insert(lines, string.format("graph %s", orientations[orientation]))
	
	-- Liste des nœuds membres de chaque groupe, construite en même temps
	-- qu’on itère sur les nœuds pour les insérer dans la sortie
	local groups_members = {}
	
	for key, _ in pairs(groups) do
		groups_members[key] = {}
	end
	
	-- Génère les nœuds
	for key, properties in pairs(nodes) do
		local id = generate_id(key)
		local name = properties.name or key
		local shape = properties.shape or "rectangle"
		
		if properties.group then
			table.insert(groups_members[properties.group], id)
		end
		
		if not nodes_shapes[shape] then
			error(string.format(
				'Forme "%s" inconnue pour le nœud "%s". Les formes possibles sont %s.',
				shape, name, get_table_keys(nodes_shapes)
			), 0)
		end
		
		-- Forme et étiquette du nœud
		table.insert(lines, string.format(nodes_shapes[shape], id, escape_quotes(name)))
		
		-- Style
		if properties.style then
			local rules = {}
			
			for property, value in pairs(properties.style) do
				table.insert(rules, string.format("%s:%s", property, value))
			end
			
			table.insert(lines, string.format("style %s %s", id, table.concat(rules, ",")))
		end
	end
	
	-- Génère les groupes
	for key, properties in pairs(groups) do
		local name = properties.name or key
		
		table.insert(lines, string.format('subgraph %s', name))
		
		for _, node in pairs(groups_members[key]) do
			table.insert(lines, node)
		end
		
		table.insert(lines, "end")
	end
	
	-- Génère les liens
	local link_number = 0
	
	for key, properties in pairs(links) do
		local from, to = split_delimiter(key, link_separator)
		local from_id = generate_id(from)
		local to_id = generate_id(to)
		
		local ending = properties.ending or "arrow"
		
		if not endings[ending] then
			error(string.format(
				'Terminaison "%s" inconnue pour le lien "%s -> %s". Les terminaisons possibles sont %s.',
				shape, from, to, get_table_keys(endings)
			), 0)
		end
		
		--- Définit les noeuds permettant de sauter de niveau.
		--- Voir https://github.com/mermaid-js/mermaid/issues/637 pour la technique
		--- qui consiste en rajouter des noeuds invisibles.
		local level = nil
		--- if to == nil then level = nil
		--- else level = nodes.to.level end
		
		if not level == '-' or not level == nil then
			level = tonumber(level)
			
			if(level == nil or level < 1) then
				error(string.format('Le niveau du noeud %s doit être un nombre entier > 0.', to), 0)
			end
			
			--- Premier noeud invisible.
			table.insert(lines, from_id .. " --- " .. from_id .. '-0')
			table.insert(lines, from_id .. '-0(" ")')
			local class = "class " .. from_id .. '-0'
			
			for i = 1, level do
				--- Ajoute un noeud invisible.
				class = class .. ',' .. from_id .. '-' .. i
				table.insert(lines, from_id .. '-' .. i .. '(" ")')
				
				--- Ajoute une connection entre les noeuds invisibles.
				table.insert(lines, from_id .. '-' .. (i - 1) .. " --- " .. from_id .. '-' .. i )
			end
			
			--- Classe CSS pour les noeuds invisibles
			table.insert(lines, class .. " SkipLevel")
			table.insert(lines, "classDef SkipLevel width:0px;")
			
			from_id = from_id .. '-' .. i --- La dernière connection sera créée normalement.
		end
		
		if properties.name then
			table.insert(lines, string.format('%s -- "%s" %s %s', from_id, escape_quotes(properties.name), endings[ending], to_id))
		else
			table.insert(lines, string.format("%s %s %s", from_id, endings[ending], to_id))
		end
		
		local shape = properties.shape or "curve"
		
		if not links_shapes[shape] then
			error(string.format(
				'Forme "%s" inconnue pour le lien "%s -> %s". Les formes possibles sont %s.',
				shape, from, to, get_table_keys(links_shapes)
			), 0)
		end
		
		if shape ~= "linear" then
			table.insert(lines, string.format("linkStyle %d interpolate %s", link_number, links_shapes[shape]))
		end
		
		link_number = link_number + 1
	end
	
	return table.concat(lines, "\n")
end

local themes = {
	default = true,
	neutral = true,
	forest = true,
	dark = true,
}

--- Appelle l’extension Mermaid
function p.render_mermaid(frame, params, nodes, groups, links)
	local mermaid = p.flowchart_to_mermaid(params, nodes, groups, links)
	local theme = params.theme or "neutral"
	
	if not themes[theme] then
		error(string.format(
			'Thème "%s" inconnu pour le flowchart. Les thèmes possibles sont %s.',
			theme, get_table_keys(themes)
		), 0)
	end
	
	local rendered_mermaid = frame:callParserFunction(
		"#mermaid", mermaid,
		"config.theme = " .. theme
	)
	
	if params.debug then
		dump_params = mw.text.nowiki(mw.dumpObject(params))
		dump_nodes = mw.text.nowiki(mw.dumpObject(nodes))
		dump_groups = mw.text.nowiki(mw.dumpObject(groups))
		dump_links = mw.text.nowiki(mw.dumpObject(links))
		dump_mermaid = mw.text.nowiki(mermaid)
		
		return string.format([[
			<h4>Représentation interne</h4>
			
			<table>
				<tr>
					<th>params</th>
					<th>nodes</th>
					<th>groups</th>
					<th>links</th>
				</tr>
				<tr>
					<td><pre>%s</pre></td>
					<td><pre>%s</pre></td>
					<td><pre>%s</pre></td>
					<td><pre>%s</pre></td>
				</tr>
			</table>
			
			<h4>Code Mermaid</h4>
			
			<pre>%s</pre>
			
			<h4>Résultat</h4>
			
			%s
		]], dump_params, dump_nodes, dump_groups, dump_links, dump_mermaid, rendered_mermaid)
	else
		return rendered_mermaid
	end
end

--[======[ Interface ]======]--

function p.render(frame)
	-- Normalise les arguments
	local args = getArgs(frame, {
		wrappers = 'Modèle:Flowchart'
	})

	-- Lit et consomme les paramètres généraux du flowchart préfixés par "$"
	local params = {}
	
	for args_key, value in pairs(args) do
		local key = args_key
		
		if type(args_key) == "number" then
			key = value
			value = "true"
		end
		
		if string.sub(key, 1, 1) == "$" then
			params[string.sub(key, 2)] = value
			args[args_key] = nil
		end
	end
	
	local nodes, groups, links = p.read_flowchart(args)
	return p.render_mermaid(frame, params, nodes, groups, links)
end

return p