Extension:Graph/Guide

From mediawiki.org

This page is for the community to write best practices using Graph extension. See also the Interactive Graph Tutorial

Organizing graphs in a wiki[edit]

Keep graphs separate from the articles[edit]

Even though graphs can be stored inside the article, it is a far better approach to store them as separate templates, e.g. Template:Graph:<yourgraphname>, and can be inserted with

{{Graph:<yourgraphname>}}

Make graphs customizable[edit]

Even though you can embed graph settings directly in the graph, it is generally a bad design to do so. Graphs should be made as flexible templates that can be reused in multiple articles with different sets of data and styling. For example, even though graph's width and height could be hardcoded:

"width": 400, "height": 200,   // bad design

it should be made as template parameters with default values, allowing for easy customization:

{{#tag:graph|{
    ...
    "width": {{{width|400}}}, "height": {{{height|200}}},   // good design
    ...
} }}

Keep data separate from the graph[edit]

Just like styling above, data itself should not be stored within the graph. Lets consider a page with a large table that can benefit from a graph, e.g. the list of most expensive paintings. The page contains a fairly complex table with data:
{|
! Price 
! Painting 
! Image 
! Artist 
! Year 
! ...
|-
! data-sort-value="$300"|~$300
| ''[[When Will You Marry?|Nafea Faa Ipoipo (When Will You Marry?)]]''
| [[File:Paul Gauguin, Nafea Faa Ipoipo? (When Will You Marry?) 1892, oil on canvas, 101 x 77 cm.jpg|95px]]
| {{sort|Gaugin, Paul|[[Paul Gauguin]]}}
| 1892
...
|}

The simple approach to build a graph would be to extract the needed data, and place the graph with that data next to the table. Even though this would work, this approach is not very good because there is now two copies of the same data, and both need to be updated when something changes. Also, if the graph is inside the text, the article becomes even longer and filled with graph code and data - fewer people will be able to edit it. If the graph is placed in a separate template, the editors are more likely to forget to update it when changing the data.

The proper solution, despite being a bit more difficult at first, will provide a much more future-proof and easier to manage path, and can be reused for all such lists.

  1. Set up a separate page with the raw data, preferably in JSON format. The structure of that file is determined by the data needed by the list.
  2. Create a Lua module with a function that converts that raw data into a well formatted wiki table.
  3. In the same Lua module create another function that extracts data needed for the graph, and outputs it as JSON-formatted data.
  4. Create the graph template that can consume the above data and plot it
  5. Lastly, insert these two snippets into the list page:

inserts well-formatted table with data:

{{#invoke:MyLuaModule|BuildTable|PageWithSourceData|...}}

inserts graph with the same data:

{{Graph:MyGraph | {{#invoke:MyLuaModule|ExtractGraphData|PageWithSourceData|...}} }}

External data[edit]

Graph data can be embedded inside the graph definition, or provided via a "url" link. Unlike the standard Vega, the graph tag requires all external links to use one of the custom wiki protocols:

  • tabular:///PageWithTabularDataOnCommons.tab - Gets tabular dataset from Commons. Should be used with "format": {"type": "json", "property": "data"} (or "meta" or "fields"). The data given to the graph is already localized to the wiki language.

For example, this dataset would be given to the graph in this format:

{
  "meta": [{
    "description": "Women's weekly earnings as a percent of men's by age, annual averages",
    "license_code": "CC0-1.0+",
    "license_text": "Creative Commons Zero",
    "license_url": "https://creativecommons.org/publicdomain/zero/1.0/",
    "sources": "Copied from [https://www.bls.gov/opub/ted/2006/oct/wk1/art02.txt bls.gov] (United States Department of Labor)"
  }],

  "fields": [{
    "name": "year",
    "type": "number",
    "title": "Year"
  }, {
    "name": "age_16_24",
    "type": "number",
    "title": "16–24 y/o"
  }, ... ],

  "data": [{
    "year": 1979,
    "age_16_24": 78.5,
      ...
  }, ...]
}
  • map:///PageWithGeoJsonDataOnCommons.map - Gets map dataset from Commons. Should be used with "format": {"type": "json", "property": "data.features"} (can also use "meta" to get the same information as for tabular). The data given to the graph is already localized to the wiki language.
  • wikifile:///Albert_Einstein_photo_1921.jpg - Gets File:Albert_Einstein_photo_1921.jpg image from Commons for an image mark. It is always recommended to add ?width=... (and/or height) to make the image the right size and reduce server load.
  • OBSOLETE, use wikifile: instead wikirawupload:{{filepath:Albert Einstein photo 1921.jpg}} - Get an image for the graph, e.g. from commons. This tag specifies any content from the uploads.* domain, without query params. Use filepath magic keyword to get file path and optional size of the image.
  • wikiraw:///Article%20title/subpage - Get raw content of a wiki page, where the path is the title of the page. The domain is optional, and if specified can be any Wikimedia host such as en.wikipedia.org, commons.wikimedia.org, ...
  • wikiapi:///?action=query&list=allpages - Get data from the MediaWiki API. The domain is optional.
  • wikirest:///api/rest_v1/page/... - Call to RESTbase API. The domain is optional.
  • geoshape:///?ids=408,664 or geoshape:///?query=SELECT... - Get geoshapes (as topojson) of the given regions, using Wikidata IDs, or a URL-encoded query (in SPARQL), with "id" column specifying geoshapes to get.
  • mapsnapshot:///?width=__&height=__&zoom=__&lat=__&lon=__ [&style=__] - Makes a link to the map snapshot service, so that a static map can be drawn for a given location/zoom/size. Parameters are converted it into a snapshot image request for Kartotherian - https://maps.wikimedia.org/img/{style},{zoom},{lat},{lon},{width}x{height}@2x.png

Examples of charts with external data[edit]

To see working example of graph json which uses external data it is possible to use Template:Graph:Lines and Template:Graph:Stacked template's debug= parameter. For example below template invocation

{{Graph:Lines
| debug=1
| table=bls.gov/US Women's weekly earnings as a percent of men's by age, annual averages.tab
| type=year | xField=year
| series="age_16_24", "age_25_34", "age_35_44", "age_45_54"
| yZero=false | yMin=50 | yMax=100
}}

will produce following graph consistent json

{
  //
  // ATTENTION: This code is maintained at https://www.mediawiki.org/wiki/Template:Graph:Lines
  //            Please do not modify it anywhere else, as it may get copied and override your changes.
  //            Suggestions can be made at https://www.mediawiki.org/wiki/Template_talk:Graph:Lines
  //
  // Template translation is in https://commons.wikimedia.org/wiki/Data:Original/Template:Graphs.tab
  //

  "version": 5,
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "width": 400,
  "height": 300,
"padding": "strict",
  "signals": [{"name": "rightwidth", "update": "width + padding.right", "value":"400" }],
  "data": [{
    "name": "chart",
    "url": "https://commons.wikimedia.org/w/api.php?action=jsondata&formatversion=2&title=bls.gov%2FUS+Women%27s+weekly+earnings+as+a+percent+of+men%27s+by+age%2C+annual+averages.tab&format=json&origin=*",
    "format": {"type": "json"
      , "property": "data"
      
    },
    "transform": [


      // Convert xField parameter into a field "_xfield"
      {"type": "formula", "as": "_xfield", "expr":
"datetime(datum.year, 0, 1)"
      },
      {"type": "collect", "sort": {"field": ["_xfield"]} }
, {"type": "fold", "fields": ["age_16_24", "age_25_34", "age_35_44", "age_45_54"]}
, {"type": "formula", "as": "_yfield", "expr": "datum.value" }

    ]
  },
// source of labels for `tabletype=tab`
  {
    "name": "labels",
    "url": "https://commons.wikimedia.org/w/api.php?action=jsondata&formatversion=2&title=bls.gov%2FUS+Women%27s+weekly+earnings+as+a+percent+of+men%27s+by+age%2C+annual+averages.tab&format=json&origin=*",
    "format": {"type": "json", "property": "fields"},
    "transform": [


    ]
  },



  ],
  "scales": [
    {
      "name": "x",
      "type": "time",
      "domain": {"data": "chart", "field": "_xfield"},
      "range": "width",
      

      
    },
    {
      "name": "y",
      "type": "linear",
      "range": "height",
      "domain": {"data": "chart", "field": "_yfield"},
      "zero": false,
      "domainMin": 50,
      "domainMax": 100,
      
    },
    {
      "name": "color",
      "type": "ordinal",
      "range": {"scheme": "category10"}, 
      "domain": {"data": "chart", "field": "key"}
    },
{
      "name": "labels",
      "type": "ordinal",
"domain": {"data": "labels", "field": "name"},
      "range": {"data": "labels", "field": "title"},
    }
  ],

  "axes": [
    {
      "scale": "x", "orient": "bottom", "tickSize": 0,
      "tickCount": 7,
      
      
      
      "encode": { 
        "update": { "labels": { 
          
          
        } } 
       }
    },
    {
      "scale": "y", "orient": "left", "tickSize": 0,
      
      
      
      
      "encode": { 
        "labels": { 
          
          
        } 
       }
    }
  ],

  "marks": [
    // Group data by the group parameter or "key", and draw lines, one line per group
    {
      "type": "group",
      "from": {
        "facet": {
          "name": ["key"],
          "data": "chart",
          "groupby": ["key"]
        }
      },
      "marks": [
        {
          "type": "line",
          "from": {"data": ["key"]},
          "encode": {
            "hover": {
              "stroke": {"value": "red"}
            },
            "update": {
              "stroke": {"scale": "color", "field": "key"}
            },
            "enter": {
              "y": {"scale": "y", "field": "_yfield"},
              "x": {"scale": "x", "field": "_xfield"},
              "stroke": {"scale": "color", "field": "key"},
              "interpolate": {"value": "linear"},
              "strokeWidth": {"value": 2.5}
            }
          }
        }
        
      ],
    },





  ]
}
See or edit raw graph data.

Frequent problems[edit]

Reminder about nested template parameters[edit]

Templates require you to separate nested template parameters by at least one character, otherwise the graph with simply not display and you will get a syntax error. Be careful!

"values": {{{1|{{#invoke:Graph:Utils|expandDict|{"BR":"pink","US":"blue"} }}}}} // will not render
"values": {{{1|{{#invoke:Graph:Utils|expandDict|{"BR":"pink","US":"blue"} }} }}} // will render, notice the whitespace in the closing braces

Using double curly braces in the graphs[edit]

Vega 2 supports template parameters, such as "template": "{{indexDate | time: '%b %Y'}}", but MediaWiki will attempt to find "indexDate" template and transclude it. To avoid this, use \u007b instead of the first curly brace, \u007d instead of the last closing curly brace, and the {{!}} instead of the pipe symbol "|": : "\u007b{indexDate {{!}} time: '%b %Y'}\u007d"

Debugging[edit]

  • The best place to develop and debug is in the Graph Sandbox. Copy JSON code to see the graph change in real time as you type. Graph sandbox understands json comments and expands wiki markup. See the bottom right panel for the converted JSON.
  • To get the graph JSON from a template, while the graph is being displayed, use the following oneliner in the browser's debugger console (hit to show the JSON). If there is more than one graph on the page, increment the [0] until you get the one you want. Remove first and last quote, and copy/paste to the Vega editor above.
JSON.stringify(Object.values(mw.config.get("wgGraphSpecs"))[0])
  • In Google Chrome, this will copy the string directly onto the clipboard [1]:
copy(JSON.stringify(Object.values(mw.config.get("wgGraphSpecs"))[0]))
  • You can also use browser's debugger to inspect the graph you are interested in (even if it is not showing) - you should see something like <div class="mw-graph ... data-graph-id="834d6921e69e66a00b40d8606e39e414145f8288" .... Copy the hash value, hit escape (in Chrome), and use this oneliner to show the JSON (replacing the shown hash value with your own). Remove first and last quote, and copy/paste to the Vega editor above.
JSON.stringify(mw.config.get('wgGraphSpecs')['834d6921e69e66a00b40d8606e39e414145f8288'])