Extension:External Data/Local programs/examples

From mediawiki.org

This is a list of examples for the parser function {{#get_program_data:}}. The original wiki page is at https://traditio.wiki/Traditio:Test/ED/exe; real output for the wikitext is provided there.

Program Settings Wikicode Screenshot
man
// man
$wgExternalDataSources['man'] = [
   'command'       => 'man $topic$',
   'params'        => [ 'topic' ],
   'param filters' => [ 'topic' => '/^\w+$/' ]
];
{{#get_program_data:
    program = man
  | topic = uname
  | data = manual=__text
  | format = text
 }}<blockquote>{{#external_value:manual}}</blockquote>
whatis
// whatis
$wgExternalDataSources['whatis'] = [
   'command'       => 'whatis $topic$',
   'params'        => [ 'topic' ],
   'param filters' => [ 'topic' => '/^\w+$/' ]
];
{{#get_program_data:
    program = whatis
  | topic = uname
  | data = summary=__text
  | format = text
 }}<blockquote>{{#external_value:summary}}</blockquote>
whois
// whois
$wgExternalDataSources['whois'] = [
   'command'       => 'whois $domain$',
   'params'        => [ 'domain' ],
   'param filters' => [ 'domain' => '/^[\w\.]+$/' ]
];
{{#get_program_data:
    program = whois
  | domain = mediawiki.org
  | data = report=__text
  | format = text
 }}{{#tag:pre|{{#external_value:report}}}}
geoiplookup
// geoiplookup
$wgExternalDataSources['geoiplookup'] = [
   'command'       => 'geoiplookup $domain$',
   'params'        => [ 'domain' ],
   'param filters' => [ 'domain' => '/^[\w\.]+$/' ]
];
{{#get_program_data:
    program = geoiplookup
  | domain = mediawiki.org
  | data = report=__text
  | format = text
 }}<blockquote>{{#external_value:report}}</blockquote>
locale

(shows $wgShellLocale anyway)

// locale
$wgExternalDataSources['locale'] = [
   'env'           => [ 'LANG' => '$locale$' ],
   'command'       => 'locale -k $category$',
   'params'        => [ 'locale' => 'POSIX', 'category' ],
   'param filters' => [ 'locale' => '/^\w+(\.\w+)?$/', 'category' => '/^LC_[A-Z]+$/' ]
];
{{#get_program_data:
    program = locale
  | locale = ru_RU.utf8
  | category = LC_TELEPHONE
  | data = name=1,value=2
  | format = CSV
  | delimiter = =
 }}{| class="wikitable"
 ! Name !! Value {{#for_external_table:<nowiki/>
 {{!}}-
 {{!}} {{{name}}} {{!}}{{!}} {{{value}}}
 }}
 |}
apt-cache show
// apt-cache show
$wgExternalDataSources['apt-cache show'] = [
   'command'       => 'apt-cache show $package$',
   'params'        => [ 'package' ],
   'param filters' => [ 'package' => '/^[\w-]+$/' ]
];
{{#get_program_data:
    program = apt-cache show
  | package = graphviz-doc
  | data = key=1,value=2
  | format = CSV
  | delimiter = :
}}
{| class="wikitable"
! Key !! Value {{#for_external_table:<nowiki/>
{{!}}-
{{!}} {{{key}}} {{!}}{{!}} {{{value}}}
}}
|}
apt-cache showpkg
// apt-cache showpkg
$wgExternalDataSources['apt-cache showpkg'] = [
   'command'       => 'apt-cache showpkg $package$',
   'params'        => [ 'package' ],
   'param filters' => [ 'package' => '/^[\w-]+$/' ]
];
{{#get_program_data:
    program = apt-cache showpkg
  | package = graphviz-doc
  | data = info=__text
  | format = text
 }}{{#tag:pre|{{#external_value:info}}}}
apt-cache depends
// apt-cache depends
$wgExternalDataSources['apt-cache depends'] = [
   'command'       => 'apt-cache depends $package$',
   'params'        => [ 'package' ],
   'param filters' => [ 'package' => '/^[\w-]+$/' ]
];
{{#get_program_data:
    program = apt-cache depends
  | package = graphviz
  | data = info=__text
  | format = text
 }}{{#tag:pre|{{#external_value:info}}}}
composer show
// composer show
$wgExternalDataSources['composer show'] = [
   'command'       => 'composer show $package$',
   'params'        => [ 'package' ],
   'param filters' => [ 'package' => '%^[\w/-]+$%' ]
];
{{#get_program_data:
    program = composer show
  | package = mediawiki/graph-viz
  | data = info=__text
  | format = text
 }}{{#tag:pre|{{#external_value:info}}}}
tzdata
// Time zones:
/*
echo > /var/www/scripts/tzdata <<'SCRIPT'
#!/bin/bash
echo -e 'name,weekday,month,day,time,year,abbr'
timedatectl list-timezones | xargs zdump | sed -E 's/\s+/,/g'
SCRIPT
sudo chmod +x /var/www/scripts/tzdata
 */
$wgExternalDataSources['tzdata'] = [
	'name'      => 'tzdata',
	'command'   => '/var/www/scripts/tzdata',
	'min cache seconds' => 30 * 24 * 60 * 60
];
{|
! name !! weekday !! month !! day !! time !! year !! abbr
{{#for_external_table:|
{{!}}-
{{!}} {{{name}}} {{!}}{{!}} {{{weekday}}} {{!}}{{!}} {{{month}}} {{!}}{{!}} {{{day}}} {{!}}{{!}} {{{time}}} {{!}}{{!}} {{{year}}} {{!}}{{!}} {{{abbr}}}
| source = tzdata
}}
|}
GraphViz, tag emulation mode
// GraphViz:
// sudo apt install graphviz
$wgExternalDataSources['graphviz'] = [
   'name'              => 'GraphViz',
   'program url'       => 'https://graphviz.org/',
   'version command'   => null,
   'command'           => 'dot -K$layout$ -Tsvg',
   'params'            => [ 'layout' => 'dot' ],
   'param filters'     => [ 'layout' => '/^(dot|neato|twopi|circo|fdp|osage|patchwork|sfdp)$/' ],
   'input'             => 'dot',
   'preprocess'        => 'EDConnectorExe::wikilinks4dot',
   'postprocess'       => 'EDConnectorExe::innerXML',
   'min cache seconds' => 30 * 24 * 60 * 60,
   'tag'               => 'graphviz'
];
<graphviz>
digraph example3 {
  node [shape=plaintext];
  Mollusca [URL="[[w:Mollusca]]"];
  Neomeniomorpha [URL="[[w:Neomeniomorpha]]"];
  X1 [shape=point,label=""];
  Caudofoveata [URL="[[w:Caudofoveata]]"];
  Testaria [URL="[[w:Testaria]]"];
  Polyplacophora [URL="[[w:Polyplacophora]]"];
  Conchifera [URL="[[w:Conchifera]]"];
  Tryblidiida [URL="[[w:Tryblidiida]]"];
  Ganglioneura [URL="[[w:Ganglioneura]]"];
  Bivalvia [URL="[[w:Bivalvia]]"];
  X2 [shape=point,label=""];
  X3 [shape=point,label=""];
  Scaphopoda [URL="[[w:Scaphopoda]]"];
  Cephalopoda [URL="[[w:Cephalopoda]]"];
  Gastropoda [URL="[[w:Gastropoda]]"];
  Mollusca->X1->Testaria->Conchifera->Ganglioneura->X2->Gastropoda
  Mollusca->Neomeniomorpha
  X1->Caudofoveata
  Testaria->Polyplacophora
  Conchifera->Tryblidiida
  Ganglioneura ->Bivalvia
  X2->X3->Cephalopoda
  X3->Scaphopoda
}

</graphviz>

mscgen, tag emulation mode
// mscgen:
// sudo apt install mscgen
$wgExternalDataSources['mscgen'] = [
   'name'              => 'mscgen',
   'program url'       => 'https://www.mcternan.me.uk/mscgen/',
   'version'           => 'Mscgen version 0.20, Copyright (C) 2010 Michael C McTernan, Michael.McTernan.2001@cs.bris.ac.uk',
   'command'           => 'mscgen -Tsvg -o -',
   'input'             => 'dot',
   'preprocess'        => 'EDConnectorExe::wikilinks4dot',
   'postprocess'       => 'EDConnectorExe::innerXML',
   'min cache seconds' => 30 * 24 * 60 * 60,
   'tag'               => 'mscgen'
];
<mscgen>
msc {
  a,b,c;

  a->b  [label="ab()"];
  b->c  [label="bc(TRUE)"];
  c=>c  [label="process(1)"];
  c=>c  [label="process(2)"];
  ...;
  c=>c  [label="process(n)"];
  c=>c  [label="process(END)"];
  a<<=c [label="callback()"];
  ---   [label="If more to run", ID="*"];
  a->a  [label="next()"];
  a->c  [label="ac()"];
  b<-c  [label="cb(TRUE)"];
  b->b  [label="stalled(...)"];
  a<-b  [label="ab() = FALSE"];
}

</mscgen

PlantUML
// PlantUML
// sudo wget https://downloads.sourceforge.net/project/plantuml/plantuml.jar -P /usr/share/java
// wget http://beta.plantuml.net/plantuml-jlatexmath.zip && sudo unzip plantuml-jlatexmath.zip -d /usr/share/java
$wgExternalDataSources['plantuml'] = [
	'name'				=> 'PlantUML',
	'program url'		=> 'https://plantuml.com',
	'version command'	=> 'java -jar /usr/share/java/plantuml.jar -version',
	'command'			=> 'java -jar /usr/share/java/plantuml.jar -tsvg -charset UTF-8 -p',
	'env'				=> [ 'LOG4J_FORMAT_MSG_NO_LOOKUPS' => true ],
	'limits'			=> [ 'memory' => 0 ],
	'params'			=> [ 'uml' ],
	'input'				=> 'uml',
	'preprocess'		=> 'EDConnectorExe::wikilinks4uml',
	'postprocess'		=> 'EDConnectorExe::innerXML',
	'min cache seconds'	=> 30 * 24 * 60 * 60,
	'tag'				=> 'plantuml'
];
<plantuml>@startuml
 participant User
 User -> A: DoWork
 activate A #FFBBBB
 A -> A: Internal call
 activate A #DarkSalmon
 A -> B: << createRequest >>
 activate B
 B --> A: RequestCreated
 deactivate B
 deactivate A
 A -> User: Done
 deactivate A
 @enduml</plantuml>

-

ploticus, tag emulation mode
// ploticus
// sudo apt install ploticus
$wgExternalDataSources['ploticus'] = [
   'name'              => 'ploticus',
   'program url'       => 'http://ploticus.sourceforge.net/doc/welcome.html',
   'version'           => 'ploticus 2.42-May2013 (unix).  This build can produce: PS EPS SVG SVGZ X11 PNG JPEG WBMP FreeType2. Copyright 1998-2009 Steve Grubb',
   'command'           => 'ploticus -stdin -scale $scale$ -textsize $fontsize$ -svg -omit_xml_declaration -xml_encoding utf-8 -noshell -csmap -o stdout',
   'env'               => [ 'TDH_ERRMODE' => 'cgi' ],
   'params'            => [ 'scale' => 1, 'fontsize' => 1 ],
   'param filters'     => [ 'scale' => '/^\d+(\.\d+)?$/', 'fontsize' => '/^\d+(\.\d+)?$/' ],
   'input'             => 'script',
   'postprocess'       => 'EDConnectorExe::innerXML',
   'min cache seconds' => 30 * 24 * 60 * 60,
   'tag'               => 'ploticus'
];
<ploticus scale="2" fontsize="20">#setifnotgiven CGI = "http://ploticus.sourceforge.net/cgi-bin/showcgiargs"

 #proc getdata
 data:
   DBC	8:00	9:00	"Good morning, Fatherland!"
   NBC	8:00	9:00	"Windon Water"
   NBC  9:00	9:30	"Encore! Encore!"
   NBC  9:30	10:00	"Conrad loom"
   NBC	10:00	11:00	"Trinity"
   ABC	8:00	8:30	"Secret Lives"
   ABC	8:30	9:00	"Sports Night"
   ABC	9:00	11:00	"Movie of the Week"
   CBS	8:00	8:30	"Cosby"
   CBS	8:30	9:00	"Kids say..."
   CBS	9:00	10:00	"Charmed"
   CBS	10:00	11:00	"To have and to Hold"

 #proc areadef
   title: Evening television schedule
   rectangle: 1 1 5 2
   xscaletype: time
   xrange: 08:00 11:00
   yscaletype: categories
   ycategories: 
        DBC
	NBC
	ABC
	CBS

 #proc xaxis
   stubs: inc 1 hours

 #proc yaxis
   stubs: categories

 #proc bars
   clickmapurl: /@4
   color: powderblue2
   axis: x
   locfield: 1
   segmentfields: 2 3
   labelfield: 4
   longwayslabel: yes
   labeldetails: align=left size=4</ploticus>
gnuplot, tag emulation mode
// gnuplot
// sudo apt install gnuplot
// cd /var/www/js && mkdir gnuplot && cd gnuplot && wget http://gnuplot.sourceforge.net/demo_svg_5.4/gnuplot_svg.js
$wgExternalDataSources['gnuplot'] = [
   'name'              => 'gnuplot',
   'program url'       => 'http://www.gnuplot.info/',
   'version command'   => 'gnuplot -V',
   'command'           => [ 'gnuplot', '-e', 'set terminal svg size $width$,$height$ dynamic enhanced '
                           . 'font \'arial,$size$\' mousing jsdir \'/js/gnuplot\' name \'$name$\' $heads$ dashlength 1.0; ', '-' ],
   'params'            => [ 'width' => 600, 'height' => 400, 'size' => 10, 'name' => 'gnuplot', 'heads' => 'butt' ],
   'param filters'     => [ 'width' => '/^\d+$/', 'height' => '/^\d+$/', 'size' => '/^\d+$/', 'heads' => '/^(rounded|butt|square)$/' ],
   'input'             => 'script',
   'postprocess'       => 'EDConnectorExe::innerXML',
   'min cache seconds' => 30 * 24 * 60 * 60,
   'tag'               => 'gnuplot'
];
<gnuplot>set dummy u, v
 set key bmargin center horizontal Right noreverse enhanced autotitle nobox
 set parametric
 set view 45, 50, 1, 1
 set isosamples 50, 10
 set hidden3d back offset 1 trianglepattern 3 undefined 1 altdiagonal bentover
 set style data lines
 set ztics  norangelimit -1.00000,0.25,1.00000
 set title "Parametric Sphere" 
 set urange [ -1.57080 : 1.57080 ] noreverse nowriteback
 set vrange [ 0.00000 : 6.28319 ] noreverse nowriteback
 set xrange [ * : * ] noreverse writeback
 set x2range [ * : * ] noreverse writeback
 set yrange [ * : * ] noreverse writeback
 set y2range [ * : * ] noreverse writeback
 set zrange [ * : * ] noreverse writeback
 set cbrange [ * : * ] noreverse writeback
 et rrange [ * : * ] noreverse writeback
 set colorbox vertical origin screen 0.9, 0.2 size screen 0.05, 0.6 front  noinvert bdefault
 VoxelDistance = 9.00500479207635e-308
 #NO_ANIMATION = 1
 splot cos(u)*cos(v),cos(u)*sin(v),sin(u)</gnuplot>
LilyPond, tag emulation mode
// LilyPond
// sudo apt install lilypond
$wgExternalDataSources['lilypond'] = [
   'name'              => 'LilyPond',
   'program url'       => 'http://lilypond.org/',
   'version command'   => 'lilypond -v',
   'command'           => 'lilypond -dbackend=svg -dpaper-size="$size$" -dsafe -dcrop -o $tmp$ -',
   'temp'              => '$tmp$.cropped.svg',
   'ignore warnings'   => true,
   'params'            => [ 'size' => 'a4' ],
   'param filters'     => [ 'size' => '/^\w+$/' ],
   'input'             => 'score',
   'min cache seconds' => 30 * 24 * 60 * 60,
   'tag'               => 'score'
];
<score>\paper {
  indent = 0\mm
  line-width = 110\mm
  oddHeaderMarkup = ""
  evenHeaderMarkup = ""
  oddFooterMarkup = ""
  evenFooterMarkup = ""
}

\relative c' { f d f a d f e d cis a cis e a g f e }</score>
Vega
// Vega
// In /var/www/js
// npm install vega-cli vega-embed vega-lite vega-projection-extended
// /var/www/js/vega/config.json
/*
{
   "locale": {
   "number": {
      "decimal": ",",
         "thousands": "\u00a0",
         "grouping": [3],
         "currency": ["", "\u00a0₽"]
      },
      "time": {
      "dateTime": "%A, %e %B %Y г. %X",
         "date": "%d.%m.%Y",
         "time": "%H:%M:%S",
         "periods": ["AM", "PM"],
         "days": ["воскресенье", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота"],
         "shortDays": ["вс", "пн", "вт", "ср", "чт", "пт", "сб"],
         "months": ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"],
         "shortMonths": ["янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек"]
      }
   }
}
*/
$wgExternalDataSources['vega'] = [
   'name'              => 'Vega',
   'program url'       => 'https://vega.github.io',
   'version command'   => '/var/www/js/node_modules/vega-cli/bin/vg2svg --version',
   'command'           => "/var/www/js/node_modules/vega-cli/bin/vg2svg -c /var/www/js/vega/config.json -b $wgUploadDirectory",
   'params'            => [ 'json', 'width' => 600, 'height' => 600 ],
   'param filters'     => [
      'json' => static function( string $json ) {
         return is_array( $json ) || is_string( $json ) && json_decode( $json );
      },
      'width' => '/^\d+$/', 'height' => '/^\d+$/',
   ],
   'input'             => 'json',
   'preprocess'        => function( $json ) {
      return is_array( $json ) ? json_encode( $json ) : $json;
   },
   'postprocess'       => static function ( $svg, array $params ) {
      $json = $params['json'];
      $id = hash( 'fnv1a64', $json );
      if ( is_string ( $json ) ) {
         // Decode JSON.
         $result = \FormatJson::parse( $json, FormatJson::TRY_FIXING | FormatJson::STRIP_COMMENTS );
         if ( !$result->isOK() ) {
            return EDParserFunctions::formatErrorMessages( 'JSON could not be parsed' );
         }
         $parsed = $result->getValue();
         if ( !is_object( $parsed ) ) {
            return EDParserFunctions::formatErrorMessages( 'Parsed JSON is not an object' );
         }
      } else {
         $parsed = $json;
      }
      // Encode JSON back.
      $json = html_entity_decode( \FormatJSON::encode( $parsed, false, \FormatJSON::ALL_OK ) );
      if ( $params['width'] && $params['height'] ) {
         $style = "min-width:{$params['width']}; min-height:{$params['height']}";
      } else {
         $style ='';
      }
      return <<<HTML
<div class="vega" id="id_$id" style="$style">$svg</div>
HTML . "\n" . Html::inlineScript( <<<SCRIPT
(function () {
// Some bug injects &amp; and &#160; into json values.
const removeEntities = function ( json ) {
   const nbsp = /&#160;/g;
   const amp = /&amp;/g;
   if ( typeof json === 'string' || json instanceof String ) {
      json = json.replace( nbsp, ' ' ).replace( amp, '&' );
   } else if ( Array.isArray( json ) || typeof json === 'object' ) {
      for ( key in json ) {
         json[key] = removeEntities( json[key] );
      }
   }
   return json;
}
const spec = removeEntities( $json );
var waitForJQuery = setInterval( function() {
   if ( typeof $!== 'undefined' ) {
      $.when(
          mw.loader.getScript( '/js/node_modules/vega/build/vega.min.js' ),
          mw.loader.getScript( '/js/node_modules/vega-lite/build/vega-lite.min.js' ),
          mw.loader.getScript( '/js/node_modules/vega-embed/build/vega-embed.min.js' ),
      ).then(
          function () {
            vegaEmbed( '#id_$id', spec ).then( function( result ) {
               console.log( 'vegaEmbed result: ' + result );
            } ).catch( function( error ) {
               mw.log.error( error );
            });
          },
          function ( e ) {
              // A script failed, and is not available
              mw.log.error( e.message ); // => "Failed to load script"
          }
      );
      clearInterval( waitForJQuery );
   }
}, 10 );
} )();
SCRIPT );
   },
   'min cache seconds' => 30 * 24 * 60 * 60,
   'tag'               => 'graph'
];
<graph>{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "A configurable map of countries of the world.",
  "width": 900,
  "height": 500,
  "autosize": "none",

  "signals": [
    {
      "name": "type",
      "value": "mercator",
      "bind": {
        "input": "select",
        "options": [
          "albers",
          "albersUsa",
          "azimuthalEqualArea",
          "azimuthalEquidistant",
          "conicConformal",
          "conicEqualArea",
          "conicEquidistant",
          "equalEarth",
          "equirectangular",
          "gnomonic",
          "mercator",
          "naturalEarth1",
          "orthographic",
          "stereographic",
          "transverseMercator"
        ]
      }
    },
    { "name": "scale", "value": 150,
      "bind": {"input": "range", "min": 50, "max": 2000, "step": 1} },
    { "name": "rotate0", "value": 0,
      "bind": {"input": "range", "min": -180, "max": 180, "step": 1} },
    { "name": "rotate1", "value": 0,
      "bind": {"input": "range", "min": -90, "max": 90, "step": 1} },
    { "name": "rotate2", "value": 0,
      "bind": {"input": "range", "min": -180, "max": 180, "step": 1} },
    { "name": "center0", "value": 0,
      "bind": {"input": "range", "min": -180, "max": 180, "step": 1} },
    { "name": "center1", "value": 0,
      "bind": {"input": "range", "min": -90, "max": 90, "step": 1} },
    { "name": "translate0", "update": "width / 2" },
    { "name": "translate1", "update": "height / 2" },

    { "name": "graticuleDash", "value": 0,
      "bind": {"input": "radio", "options": [0, 3, 5, 10]} },
    { "name": "borderWidth", "value": 1,
      "bind": {"input": "text"} },
    { "name": "background", "value": "#ffffff",
      "bind": {"input": "color"} },
    { "name": "invert", "value": false,
      "bind": {"input": "checkbox"} }
  ],

  "projections": [
    {
      "name": "projection",
      "type": {"signal": "type"},
      "scale": {"signal": "scale"},
      "rotate": [
        {"signal": "rotate0"},
        {"signal": "rotate1"},
        {"signal": "rotate2"}
      ],
      "center": [
        {"signal": "center0"},
        {"signal": "center1"}
      ],
      "translate": [
        {"signal": "translate0"},
        {"signal": "translate1"}
      ]
    }
  ],

  "data": [
    {
      "name": "world",
      "url": "https://vega.github.io/vega/data/world-110m.json",
      "format": {
        "type": "topojson",
        "feature": "countries"
      }
    },
    {
      "name": "graticule",
      "transform": [
        { "type": "graticule" }
      ]
    }
  ],

  "marks": [
    {
      "type": "shape",
      "from": {"data": "graticule"},
      "encode": {
        "update": {
          "strokeWidth": {"value": 1},
          "strokeDash": {"signal": "[+graticuleDash, +graticuleDash]"},
          "stroke": {"signal": "invert ? '#444' : '#ddd'"},
          "fill": {"value": null}
        }
      },
      "transform": [
        { "type": "geoshape", "projection": "projection" }
      ]
    },
    {
      "type": "shape",
      "from": {"data": "world"},
      "encode": {
        "update": {
          "strokeWidth": {"signal": "+borderWidth"},
          "stroke": {"signal": "invert ? '#777' : '#bbb'"},
          "fill": {"signal": "invert ? '#fff' : '#000'"},
          "zindex": {"value": 0}
        },
        "hover": {
          "strokeWidth": {"signal": "+borderWidth + 1"},
          "stroke": {"value": "firebrick"},
          "zindex": {"value": 1}
        }
      },
      "transform": [
        { "type": "geoshape", "projection": "projection" }
      ]
    }
  ]
 }</graph>
mermaid
// in /var/www/js: npm install @mermaid-js/mermaid-cli
$wgExternalDataSources  ['mermaid'] = [
	'name'              => 'mermaid', // need fallback to data source in version report.
	'command'           => '/var/www/js/node_modules/.bin/mmdc -w $width$ -H $height$ -t $theme$ -b $background$',
	'limits'            => [ 'memory' => 0 ],
	'version command'   => '/var/www/js/node_modules/.bin/mmdc -V',
	'program url'       => 'https://mermaid-js.github.io',
	'params'            => [ 'mmd', 'width' => '800', 'height' => '600', 'theme' => 'default', 'background' => 'white' ],
	'param filters'     => [ 'width' => '/^\d+$/', 'height' => '/^\d+$/', 'theme' => '/^(default|forest|dark|neutral)$/i', 'background' => '/^(\w+|\#[0-9A-F]{6})$/i' ],
	'input'             => 'mmd',
	'temp'              => 'out.svg',
	'min cache seconds' => 30 * 24 * 60 * 60,
	'tag'               => 'mermaid'
];
<mermaid theme="forest">sequenceDiagram
    participant Alice
    participant Bob
    Alice->>John: Hello John, how are you?
    loop Healthcheck
        John->>John: Fight against hypochondria
    end
    Note right of John: Rational thoughts <br/>prevail!
    John-->>Alice: Great!
    John->>Bob: How about you?
    Bob-->>John: Jolly good!
 </mermaid>

BPMN
// In /var/www/js: npm install bpmn-to-image
/* create and chmod +x bpmn:
#!/bin/bash
input=$(mktemp /tmp/XXXXXXXXX.bpmn)
cat > $input
output=$(mktemp /tmp/XXXXXXXXX.svg)
/var/www/js/node_modules/.bin/bpmn-to-image $input:$output --min-dimensions=$1x$2 --scale=$3 --title=$4 --no-footer 1> /dev/null
cat $output
rm $input
rm $output
*/
$wgExternalDataSources['bpmn'] = [
	'name'              => 'BPMN.io',
	'program url'       => 'https://bpmn.io/',
	'version command'   => '/var/www/js/node_modules/.bin/bpmn-to-image --version',
	'command'           => '/var/www/js/bpmn $width$ $height$ $scale$ $title$',
	'limits'            => [ 'memory' => 0 ],
	'params'            => [ 'bpmn', 'width' => 400, 'height' => 300, 'scale' => 1, 'title' => 'BPMN diagram' ],
	'param filters'     => [ 'bpmn' => static function ( string $xml ): bool {
		return (bool)simplexml_load_string( $xml );
	}, 'width' => '/^\d+$/', 'height' => '/^\d+$/', 'scale' => '/^\d+(\.\d+)?$/' ],
	'input'             => 'bpmn',
	'min cache seconds' => 30 * 24 * 60 * 60,
	'tag'               => 'bpmn'
];
<bpmn>
<?xml version="1.0" standalone="yes"?>
<semantic:definitions id="_1275940932088" targetNamespace="http://www.trisotech.com/definitions/_1275940932088" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:semantic="http://www.omg.org/spec/BPMN/20100524/MODEL">
    <semantic:message id="_1275940932310"/>
    <semantic:message id="_1275940932433"/>
    <semantic:process isExecutable="false" id="_6-1">
        <semantic:laneSet id="ls_6-438">
            <semantic:lane name="clerk" id="_6-650">
                <semantic:flowNodeRef>_6-450</semantic:flowNodeRef>
                <semantic:flowNodeRef>_6-652</semantic:flowNodeRef>
                <semantic:flowNodeRef>_6-674</semantic:flowNodeRef>
                <semantic:flowNodeRef>_6-695</semantic:flowNodeRef>
            </semantic:lane>
            <semantic:lane name="pizza chef" id="_6-446">
                <semantic:flowNodeRef>_6-463</semantic:flowNodeRef>
            </semantic:lane>
            <semantic:lane name="delivery boy" id="_6-448">
                <semantic:flowNodeRef>_6-514</semantic:flowNodeRef>
                <semantic:flowNodeRef>_6-565</semantic:flowNodeRef>
                <semantic:flowNodeRef>_6-616</semantic:flowNodeRef>
            </semantic:lane>
        </semantic:laneSet>
        <semantic:startEvent name="Order received" id="_6-450">
            <semantic:outgoing>_6-630</semantic:outgoing>
            <semantic:messageEventDefinition messageRef="_1275940932310"/>
        </semantic:startEvent>
        <semantic:parallelGateway gatewayDirection="Unspecified" name="" id="_6-652">
            <semantic:incoming>_6-630</semantic:incoming>
            <semantic:outgoing>_6-691</semantic:outgoing>
            <semantic:outgoing>_6-693</semantic:outgoing>
        </semantic:parallelGateway>
        <semantic:intermediateCatchEvent parallelMultiple="false" name="&#8222;where is my pizza?&#8220;" id="_6-674">
            <semantic:incoming>_6-691</semantic:incoming>
            <semantic:incoming>_6-746</semantic:incoming>
            <semantic:outgoing>_6-748</semantic:outgoing>
            <semantic:messageEventDefinition messageRef="_1275940932433"/>
        </semantic:intermediateCatchEvent>
        <semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Calm customer" id="_6-695">
            <semantic:incoming>_6-748</semantic:incoming>
            <semantic:outgoing>_6-746</semantic:outgoing>
        </semantic:task>
        <semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Bake the pizza" id="_6-463">
            <semantic:incoming>_6-693</semantic:incoming>
            <semantic:outgoing>_6-632</semantic:outgoing>
        </semantic:task>
        <semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Deliver the pizza" id="_6-514">
            <semantic:incoming>_6-632</semantic:incoming>
            <semantic:outgoing>_6-634</semantic:outgoing>
        </semantic:task>
        <semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Receive payment" id="_6-565">
            <semantic:incoming>_6-634</semantic:incoming>
            <semantic:outgoing>_6-636</semantic:outgoing>
        </semantic:task>
        <semantic:endEvent name="" id="_6-616">
            <semantic:incoming>_6-636</semantic:incoming>
            <semantic:terminateEventDefinition/>
        </semantic:endEvent>
        <semantic:sequenceFlow sourceRef="_6-450" targetRef="_6-652" name="" id="_6-630"/>
        <semantic:sequenceFlow sourceRef="_6-463" targetRef="_6-514" name="" id="_6-632"/>
        <semantic:sequenceFlow sourceRef="_6-514" targetRef="_6-565" name="" id="_6-634"/>
        <semantic:sequenceFlow sourceRef="_6-565" targetRef="_6-616" name="" id="_6-636"/>
        <semantic:sequenceFlow sourceRef="_6-652" targetRef="_6-674" name="" id="_6-691"/>
        <semantic:sequenceFlow sourceRef="_6-652" targetRef="_6-463" name="" id="_6-693"/>
        <semantic:sequenceFlow sourceRef="_6-695" targetRef="_6-674" name="" id="_6-746"/>
        <semantic:sequenceFlow sourceRef="_6-674" targetRef="_6-695" name="" id="_6-748"/>
    </semantic:process>
    <semantic:message id="_1275940932198"/>
    <semantic:process isExecutable="false" id="_6-2">
        <semantic:startEvent name="Hungry for pizza" id="_6-61">
            <semantic:outgoing>_6-125</semantic:outgoing>
        </semantic:startEvent>
        <semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Select a pizza" id="_6-74">
            <semantic:incoming>_6-125</semantic:incoming>
            <semantic:outgoing>_6-178</semantic:outgoing>
        </semantic:task>
        <semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Order a pizza" id="_6-127">
            <semantic:incoming>_6-178</semantic:incoming>
            <semantic:outgoing>_6-420</semantic:outgoing>
        </semantic:task>
        <semantic:eventBasedGateway eventGatewayType="Exclusive" instantiate="false" gatewayDirection="Unspecified" name="" id="_6-180">
            <semantic:incoming>_6-420</semantic:incoming>
            <semantic:incoming>_6-430</semantic:incoming>
            <semantic:outgoing>_6-422</semantic:outgoing>
            <semantic:outgoing>_6-424</semantic:outgoing>
        </semantic:eventBasedGateway>
        <semantic:intermediateCatchEvent parallelMultiple="false" name="pizza received" id="_6-202">
            <semantic:incoming>_6-422</semantic:incoming>
            <semantic:outgoing>_6-428</semantic:outgoing>
            <semantic:messageEventDefinition messageRef="_1275940932198"/>
        </semantic:intermediateCatchEvent>
        <semantic:intermediateCatchEvent parallelMultiple="false" name="60 minutes" id="_6-219">
            <semantic:incoming>_6-424</semantic:incoming>
            <semantic:outgoing>_6-426</semantic:outgoing>
            <semantic:timerEventDefinition>
                <semantic:timeDate/>
            </semantic:timerEventDefinition>
        </semantic:intermediateCatchEvent>
        <semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Ask for the pizza" id="_6-236">
            <semantic:incoming>_6-426</semantic:incoming>
            <semantic:outgoing>_6-430</semantic:outgoing>
        </semantic:task>
        <semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Pay the pizza" id="_6-304">
            <semantic:incoming>_6-428</semantic:incoming>
            <semantic:outgoing>_6-434</semantic:outgoing>
        </semantic:task>
        <semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Eat the pizza" id="_6-355">
            <semantic:incoming>_6-434</semantic:incoming>
            <semantic:outgoing>_6-436</semantic:outgoing>
        </semantic:task>
        <semantic:endEvent name="Hunger satisfied" id="_6-406">
            <semantic:incoming>_6-436</semantic:incoming>
        </semantic:endEvent>
        <semantic:sequenceFlow sourceRef="_6-61" targetRef="_6-74" name="" id="_6-125"/>
        <semantic:sequenceFlow sourceRef="_6-74" targetRef="_6-127" name="" id="_6-178"/>
        <semantic:sequenceFlow sourceRef="_6-127" targetRef="_6-180" name="" id="_6-420"/>
        <semantic:sequenceFlow sourceRef="_6-180" targetRef="_6-202" name="" id="_6-422"/>
        <semantic:sequenceFlow sourceRef="_6-180" targetRef="_6-219" name="" id="_6-424"/>
        <semantic:sequenceFlow sourceRef="_6-219" targetRef="_6-236" name="" id="_6-426"/>
        <semantic:sequenceFlow sourceRef="_6-202" targetRef="_6-304" name="" id="_6-428"/>
        <semantic:sequenceFlow sourceRef="_6-236" targetRef="_6-180" name="" id="_6-430"/>
        <semantic:sequenceFlow sourceRef="_6-304" targetRef="_6-355" name="" id="_6-434"/>
        <semantic:sequenceFlow sourceRef="_6-355" targetRef="_6-406" name="" id="_6-436"/>
    </semantic:process>
    <semantic:collaboration id="C1275940932557">
        <semantic:participant name="Pizza Customer" processRef="_6-2" id="_6-53"/>
        <semantic:participant name="Pizza vendor" processRef="_6-1" id="_6-438"/>
        <semantic:messageFlow name="pizza order" sourceRef="_6-127" targetRef="_6-450" id="_6-638"/>
        <semantic:messageFlow name="" sourceRef="_6-236" targetRef="_6-674" id="_6-642"/>
        <semantic:messageFlow name="receipt" sourceRef="_6-565" targetRef="_6-304" id="_6-646"/>
        <semantic:messageFlow name="money" sourceRef="_6-304" targetRef="_6-565" id="_6-648"/>
        <semantic:messageFlow name="pizza" sourceRef="_6-514" targetRef="_6-202" id="_6-640"/>
        <semantic:messageFlow name="" sourceRef="_6-695" targetRef="_6-236" id="_6-750"/>
    </semantic:collaboration>
    <bpmndi:BPMNDiagram documentation="" id="Trisotech.Visio-_6" name="Untitled Diagram" resolution="96.00000267028808">
        <bpmndi:BPMNPlane bpmnElement="C1275940932557">
            <bpmndi:BPMNShape bpmnElement="_6-53" isHorizontal="true" id="Trisotech.Visio__6-53">
                <dc:Bounds height="294.0" width="1044.0" x="12.0" y="12.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-438" isHorizontal="true" id="Trisotech.Visio__6-438">
                <dc:Bounds height="337.0" width="905.0" x="12.0" y="372.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-650" isHorizontal="true" id="Trisotech.Visio__6__6-650">
                <dc:Bounds height="114.0" width="875.0" x="42.0" y="372.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-446" isHorizontal="true" id="Trisotech.Visio__6__6-446">
                <dc:Bounds height="114.0" width="875.0" x="42.0" y="486.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-448" isHorizontal="true" id="Trisotech.Visio__6__6-448">
                <dc:Bounds height="109.0" width="875.0" x="42.0" y="600.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-450" id="Trisotech.Visio__6__6-450">
                <dc:Bounds height="30.0" width="30.0" x="79.0" y="405.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-652" id="Trisotech.Visio__6__6-652">
                <dc:Bounds height="42.0" width="42.0" x="140.0" y="399.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-674" id="Trisotech.Visio__6__6-674">
                <dc:Bounds height="32.0" width="32.0" x="218.0" y="404.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-695" id="Trisotech.Visio__6__6-695">
                <dc:Bounds height="68.0" width="83.0" x="286.0" y="386.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-463" id="Trisotech.Visio__6__6-463">
                <dc:Bounds height="68.0" width="83.0" x="252.0" y="521.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-514" id="Trisotech.Visio__6__6-514">
                <dc:Bounds height="68.0" width="83.0" x="464.0" y="629.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-565" id="Trisotech.Visio__6__6-565">
                <dc:Bounds height="68.0" width="83.0" x="603.0" y="629.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-616" id="Trisotech.Visio__6__6-616">
                <dc:Bounds height="32.0" width="32.0" x="722.0" y="647.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-61" id="Trisotech.Visio__6__6-61">
                <dc:Bounds height="30.0" width="30.0" x="66.0" y="96.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-74" id="Trisotech.Visio__6__6-74">
                <dc:Bounds height="68.0" width="83.0" x="145.0" y="77.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-127" id="Trisotech.Visio__6__6-127">
                <dc:Bounds height="68.0" width="83.0" x="265.0" y="77.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-180" id="Trisotech.Visio__6__6-180">
                <dc:Bounds height="42.0" width="42.0" x="378.0" y="90.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-202" id="Trisotech.Visio__6__6-202">
                <dc:Bounds height="32.0" width="32.0" x="647.0" y="95.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-219" id="Trisotech.Visio__6__6-219">
                <dc:Bounds height="32.0" width="32.0" x="448.0" y="184.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-236" id="Trisotech.Visio__6__6-236">
                <dc:Bounds height="68.0" width="83.0" x="517.0" y="166.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-304" id="Trisotech.Visio__6__6-304">
                <dc:Bounds height="68.0" width="83.0" x="726.0" y="77.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-355" id="Trisotech.Visio__6__6-355">
                <dc:Bounds height="68.0" width="83.0" x="834.0" y="77.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="_6-406" id="Trisotech.Visio__6__6-406">
                <dc:Bounds height="32.0" width="32.0" x="956.0" y="95.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNEdge bpmnElement="_6-640" messageVisibleKind="initiating" id="Trisotech.Visio__6__6-640">
                <di:waypoint x="506.0" y="629.0"/>
                <di:waypoint x="506.0" y="384.0"/>
                <di:waypoint x="663.0" y="384.0"/>
                <di:waypoint x="663.0" y="127.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-630" id="Trisotech.Visio__6__6-630">
                <di:waypoint x="109.0" y="420.0"/>
                <di:waypoint x="140.0" y="420.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-691" id="Trisotech.Visio__6__6-691">
                <di:waypoint x="182.0" y="420.0"/>
                <di:waypoint x="200.0" y="420.0"/>
                <di:waypoint x="218.0" y="420.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-648" messageVisibleKind="initiating" id="Trisotech.Visio__6__6-648">
                <di:waypoint x="754.0" y="145.0"/>
                <di:waypoint x="754.0" y="408.0"/>
                <di:waypoint x="630.0" y="408.0"/>
                <di:waypoint x="631.0" y="629.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-422" id="Trisotech.Visio__6__6-422">
                <di:waypoint x="420.0" y="111.0"/>
                <di:waypoint x="438.0" y="111.0"/>
                <di:waypoint x="647.0" y="111.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-646" messageVisibleKind="non_initiating" id="Trisotech.Visio__6__6-646">
                <di:waypoint x="658.0" y="629.0"/>
                <di:waypoint x="658.0" y="432.0"/>
                <di:waypoint x="782.0" y="432.0"/>
                <di:waypoint x="782.0" y="145.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-428" id="Trisotech.Visio__6__6-428">
                <di:waypoint x="679.0" y="111.0"/>
                <di:waypoint x="726.0" y="111.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-748" id="Trisotech.Visio__6__6-748">
                <di:waypoint x="250.0" y="420.0"/>
                <di:waypoint x="268.0" y="420.0"/>
                <di:waypoint x="286.0" y="420.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-420" id="Trisotech.Visio__6__6-420">
                <di:waypoint x="348.0" y="111.0"/>
                <di:waypoint x="366.0" y="111.0"/>
                <di:waypoint x="378.0" y="111.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-636" id="Trisotech.Visio__6__6-636">
                <di:waypoint x="686.0" y="663.0"/>
                <di:waypoint x="704.0" y="663.0"/>
                <di:waypoint x="722.0" y="663.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-750" id="Trisotech.Visio__6__6-750">
                <di:waypoint x="328.0" y="386.0"/>
                <di:waypoint x="328.0" y="348.0"/>
                <di:waypoint x="572.0" y="348.0"/>
                <di:waypoint x="572.0" y="234.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-436" id="Trisotech.Visio__6__6-436">
                <di:waypoint x="918.0" y="111.0"/>
                <di:waypoint x="936.0" y="111.0"/>
                <di:waypoint x="956.0" y="111.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-632" id="Trisotech.Visio__6__6-632">
                <di:waypoint x="335.0" y="555.0"/>
                <di:waypoint x="353.0" y="555.0"/>
                <di:waypoint x="353.0" y="663.0"/>
                <di:waypoint x="464.0" y="663.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-634" id="Trisotech.Visio__6__6-634">
                <di:waypoint x="548.0" y="663.0"/>
                <di:waypoint x="603.0" y="663.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-125" id="Trisotech.Visio__6__6-125">
                <di:waypoint x="96.0" y="111.0"/>
                <di:waypoint x="114.0" y="111.0"/>
                <di:waypoint x="145.0" y="111.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-430" id="Trisotech.Visio__6__6-430">
                <di:waypoint x="600.0" y="200.0"/>
                <di:waypoint x="618.0" y="200.0"/>
                <di:waypoint x="618.0" y="252.0"/>
                <di:waypoint x="576.0" y="252.0"/>
                <di:waypoint x="549.0" y="252.0"/>
                <di:waypoint x="360.0" y="252.0"/>
                <di:waypoint x="360.0" y="111.0"/>
                <di:waypoint x="378.0" y="111.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-642" id="Trisotech.Visio__6__6-642">
                <di:waypoint x="545.0" y="234.0"/>
                <di:waypoint x="545.0" y="324.0"/>
                <di:waypoint x="234.0" y="324.0"/>
                <di:waypoint x="234.0" y="404.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-424" id="Trisotech.Visio__6__6-424">
                <di:waypoint x="399.0" y="132.0"/>
                <di:waypoint x="399.0" y="200.0"/>
                <di:waypoint x="448.0" y="200.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-638" messageVisibleKind="initiating" id="Trisotech.Visio__6__6-638">
                <di:waypoint x="306.0" y="145.0"/>
                <di:waypoint x="306.0" y="252.0"/>
                <di:waypoint x="94.0" y="252.0"/>
                <di:waypoint x="94.0" y="405.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-426" id="Trisotech.Visio__6__6-426">
                <di:waypoint x="480.0" y="200.0"/>
                <di:waypoint x="498.0" y="200.0"/>
                <di:waypoint x="517.0" y="200.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-693" id="Trisotech.Visio__6__6-693">
                <di:waypoint x="161.0" y="441.0"/>
                <di:waypoint x="161.0" y="556.0"/>
                <di:waypoint x="252.0" y="555.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-178" id="Trisotech.Visio__6__6-178">
                <di:waypoint x="228.0" y="111.0"/>
                <di:waypoint x="265.0" y="111.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="_6-746" id="Trisotech.Visio__6__6-746">
                <di:waypoint x="370.0" y="420.0"/>
                <di:waypoint x="386.0" y="420.0"/>
                <di:waypoint x="386.0" y="474.0"/>
                <di:waypoint x="191.0" y="474.0"/>
                <di:waypoint x="191.0" y="420.0"/>
                <di:waypoint x="218.0" y="420.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>

            <bpmndi:BPMNEdge bpmnElement="_6-434" id="Trisotech.Visio__6__6-434">
                <di:waypoint x="810.0" y="111.0"/>
                <di:waypoint x="834.0" y="111.0"/>
                <bpmndi:BPMNLabel/>
            </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</semantic:definitions>
Apache ECharts
// ECharts:
/* in /var/www/js:
npm install echarts --save
mkdir echarts
echo | echarts/echarts.js <<ECHARTS
const echarts = require( 'echarts' );

// Parameters:
let width = 400;
let height = 300;

process.stdin.on('data', ( data ) => {
	// Parameters:
	let width = 400;
	let height = 300;
	process.argv.forEach( ( val, index, array ) => {
		let matches = val.match( /(width|height)\s*=\s*(\d+)/ );
		if ( matches ) {
			if ( matches[1] === 'width' ) {
				width = parseInt( matches[2] );
			} else if ( matches[1] === 'height' ) {
				height = parseInt( matches[2] );
			}
		}
	});
	// In SSR mode the first container parameter is not required
	let chart = echarts.init( null, null, {
		renderer: 'svg', // must use SVG rendering mode
		ssr: true, // enable SSR
		width: width,
		height: height
	});
	// Use setOption as normal
	chart.setOption( JSON.parse( data ) );
	// Output a string
	console.log( chart.renderToSVGString() );
	// If chart is no longer useful, consider dispose it to release memory.
	chart.dispose();
	chart = null;
});
ECHARTS
*/
$wgExternalDataSources['echarts'] = [
	'name'              => 'ECharts',
	'program url'       => 'https://echarts.apache.org',
	'version command'   => 'npm -v echarts',
	'command'           => 'node /var/www/js/echarts/echarts.js width=$width$ height=$height$ < echart.json',
	'limits'            => [ 'memory' => 0 ],
	'params'            => [ 'chart', 'width' => 400, 'height' => 300 ],
	'param filters'     => [ 'chart' => static function( string $json ): bool {
		return is_array( $json ) || is_string( $json ) && json_decode( $json );
	}, 'width' => '/^\d+$/', 'height' => '/^\d+$/' ],
	'input'             => 'chart',
	'min cache seconds' => 30 * 24 * 60 * 60,
	'tag'               => 'echarts'
];

<echarts width="400" height="400">{ "tooltip": { "trigger": "item", "formatter": "{a} <br/>{b}: {c} ({d}%)" }, "legend": { "data": [ "Direct", "Marketing", "Search Engine", "Email", "Union Ads", "Video Ads", "Baidu", "Google", "Bing", "Others" ] }, "series": [ { "name": "Access From", "type": "pie", "selectedMode": "single", "radius": [0, "30%"], "label": { "position": "inner", "fontSize": 14 }, "labelLine": { "show": false }, "data": [ { "value": 1548, "name": "Search Engine" }, { "value": 775, "name": "Direct" }, { "value": 679, "name": "Marketing", "selected": true } ] }, { "name": "Access From", "type": "pie", "radius": ["45%", "60%"], "labelLine": { "length": 30 }, "label": { "formatter": "{a|{a}}{abg|}\n{hr|}\n {b|{b}:}{c} {per|{d}%} ", "backgroundColor": "#F6F8FC", "borderColor": "#8C8D8E", "borderWidth": 1, "borderRadius": 4, "rich": { "a": { "color": "#6E7079", "lineHeight": 22, "align": "center" }, "hr": { "borderColor": "#8C8D8E", "width": "100%", "borderWidth": 1, "height": 0 }, "b": { "color": "#4C5058", "fontSize": 14, "fontWeight": "bold", "lineHeight": 33 }, "per": { "color": "#fff", "backgroundColor": "#4C5058", "padding": [3, 4], "borderRadius": 4 } } }, "data": [ { "value": 1048, "name": "Baidu" }, { "value": 335, "name": "Direct" }, { "value": 310, "name": "Email" }, { "value": 251, "name": "Google" }, { "value": 234, "name": "Union Ads" }, { "value": 147, "name": "Bing" }, { "value": 135, "name": "Video Ads" }, { "value": 102, "name": "Others" } ] } ] }</echarts>