Jump to content

Module:Graph

From mediawiki.org
Module documentation

Module with helper functions for the Graph extension.

Please note: The Graph extension is not available anymore on Wikimedia wikis. It has been replaced with the Charts extension.

Functions for templates

[edit]

map

[edit]

Creates a JSON object for <graph> to display a political map with colored highlights

Maps can be found at Special:PrefixIndex/Module:Graph/ and new maps should also be saved under Module:Graph/.

Parameters:

  • basemap: sets the base map. The map definitions must follow the TopoJSON format and if saved in Wikipedia are available for this module. Maps in the default directory Special:PrefixIndex/Module:Graph/ should only be referenced by their name while omitting the Module:Graph/ prefix to allow better portability. The parameter also accepts URLs, e.g. maps from other Wikipedia versions (the link should follow the scheme of //en.wikipedia.org/w/index.php?title=mapname&action=raw, i.e. protocol-relative without leading http/s and a trailing action=raw to fetch the raw content only). URLs to maps on external sites should be avoided for the sake of link stability, performance, security, and should be assumed to be blocked by the software or browser anyway.
  • scale: the scaling factor of the map (default: 100)
  • projection: the map projection to use. Supported values are listed at https://github.com/mbostock/d3/wiki/Geo-Projections. The default value is equirectangular for an equirectangular projection.
  • ids of geographic entities: The actual parameter names depend on the base map. For example, for the above mentioned world map the ids are ISO country codes. The values can be either colors or numbers in case the geographic entities should be associated with numeric data: DE=lightblue marks Germany in light blue color, and DE=80.6 assigns Germany the value 80.6 (population in millions). In the latter case, the actual color depends on the following parameters.
    • colorScale: the color palette to use for the color scale. The palette must be provided as a comma-separated list of color values. The color values must be given either as #rgb/#rrggbb or by a CSS color name. Instead of a list, the built-in color palettes category10 and category20 can also be used.
    • scaleType: supported values are linear for a linear mapping between the data values and the color scale, log for a log mapping, pow for a power mapping (the exponent can be provided as pow 0.5), sqrt for a square-root mapping, and quantize for a quantized scale, i.e. the data is grouped in as many classes as the color palette has colors.
    • domainMin: lower boundary of the data values, i.e. smaller data values are mapped to the lower boundary
    • domainMax: upper boundary of the data values, i.e. larger data values are mapped to the upper boundary
    • legend: show color legend (does not work with quantize)
  • defaultValue: default value for unused geographic entities. In case the id values are colors the default value is silver, in case of numbers it is 0.
  • formatjson: format JSON object for better legibility

chart

[edit]

Creates a JSON object for <graph> to display charts. In the article namespace the template {{Graph:Chart}} should be used instead. See its page for use cases.

Parameters:

  • width: width of the chart
  • height: height of the chart
  • type: type of the chart: line for line charts, area for area charts, and rect for (column) bar charts, and pie for pie charts. Multiple series can stacked using the stacked prefix, e.g. stackedarea.
  • interpolate:interpolation method for line and area charts. It is recommended to use monotone for a monotone cubic interpolation – further supported values are listed at https://github.com/vega/vega/wiki/Marks#area.
  • colors: color palette of the chart as a comma-separated list of colors. The color values must be given either as #rgb/#rrggbb/#aarrggbb or by a CSS color name. For #aarrggbb the aa component denotes the alpha channel, i.e. FF=100% opacity, 80=50% opacity/transparency, etc. (The default color palette is category10).
  • xAxisTitle and yAxisTitle: captions of the x and y axes
  • xAxisMin, xAxisMax, yAxisMin, and yAxisMax: minimum and maximum values of the x and y axes
  • xAxisFormat and yAxisFormat: changes the formatting of the axis labels. Supported values are listed at https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#numbers for numbers and https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md for date/time. For example, the format % can be used to output percentages.
  • xAxisAngle: rotates the x axis labels by the specified angle. Recommended values are: -45, +45, -90, +90
  • xType and yType: Data types of the values, e.g. integer for integers, number for real numbers, date for dates (e.g. YYYY/MM/DD), and string for ordinal values.
  • x: the x-values as a comma-separated list (if a value itself contains a comma it must be escaped with a backslash, i.e. it needs to be written as \,)
  • y or y1, y2, …: the y-values for one or several data series, respectively. For pie charts y2 denotes the radiuses of the corresponding sectors.
  • legend: show legend (only works in case of multiple data series)
  • y1Title, y2Title, …: defines the label of the respective data series in the legend
  • linewidth: line width for line charts or distance between the pie segments for pie charts
  • showValues: Additionally, output the y values as text. (Currently, only (non-stacked) bar and pie charts are supported.) The output can be configured used the following parameters provided as name1:value1, name2:value2:
  • innerRadius: For pie charts: defines the inner radius to create a doughnut chart.
  • formatjson: format JSON object for better legibility

Template wrappers

[edit]

The functions mapWrapper and chartWrapper are wrappers to pass all parameters of the calling template to the respective map and chart functions.

Note: In the editor preview the graph extension creates a canvas element with vector graphics. However, when saving the page a PNG raster graphics is generated instead.

Note to developers: New functionality can be tested with the Vega Editor, that also contains a large amount of example code.

-- ATTENTION: Please edit this code at https://www.mediawiki.org/w/index.php?title=Module:Graph-- This way all wiki languages can stay in sync. Thank you!-- Changes history and TODO's moved to end of scriptlocalp={}--add debug text to this string with eg. debuglog = debuglog .. "" .. "\n\n" .. "- " .. debug.traceback() .. "result type: ".. type(result) .. " result: \n\n" .. mw.dumpObject(result) --invoke chartDebuger() to get graph JSON and this stringlocaldebuglog="Debug ".."\n\n"localbaseMapDirectory="Module:Graph/"localpersistentGrey="#54595d"localshapes={}shapes={circle="circle",x="M-.5,-.5L.5,.5M.5,-.5L-.5,.5",square="square",cross="cross",diamond="diamond",triangle_up="triangle-up",triangle_down="triangle-down",triangle_right="triangle-right",triangle_left="triangle-left",banana="m -0.5281,0.2880 0.0020,0.0192 m 0,0 c 0.1253,0.0543 0.2118,0.0679 0.3268,0.0252 0.1569,-0.0582 0.3663,-0.1636 0.4607,-0.3407 0.0824,-0.1547 0.1202,-0.2850 0.0838,-0.4794 l 0.0111,-0.1498 -0.0457,-0.0015 c -0.0024,0.3045 -0.1205,0.5674 -0.3357,0.7414 -0.1409,0.1139 -0.3227,0.1693 -0.5031,0.1856 m 0,0 c 0.1804,-0.0163 0.3622,-0.0717 0.5031,-0.1856 0.2152,-0.1739 0.3329,-0.4291 0.3357,-0.7414 l -0.0422,0.0079 c 0,0 -0.0099,0.1111 -0.0227,0.1644 -0.0537,0.1937 -0.1918,0.3355 -0.3349,0.4481 -0.1393,0.1089 -0.2717,0.2072 -0.4326,0.2806 l -0.0062,0.0260"}localfunctionnumericArray(csv)ifnotcsvthenreturnendlocallist=mw.text.split(csv,"%s*,%s*")localresult={}localisInteger=truefori=1,#listdoiflist[i]==""thenresult[i]=nilelseresult[i]=tonumber(list[i])ifnotresult[i]thenreturnendifisIntegerthenlocalint,frac=math.modf(result[i])isInteger=frac==0.0endendendreturnresult,isIntegerendlocalfunctionstringArray(text)ifnottextthenreturnendlocallist=mw.text.split(mw.ustring.gsub(tostring(text),"\\,","<COMMA>"),",",true)fori=1,#listdolist[i]=mw.ustring.gsub(mw.text.trim(list[i]),"<COMMA>",",")endreturnlistendlocalfunctionisTable(t)returntype(t)=="table"endlocalfunctioncopy(x)iftype(x)=="table"thenlocalresult={}forkey,valueinpairs(x)doresult[key]=copy(value)endreturnresultelsereturnxendendlocalfunctiondeserializeXData(serializedX,xType,xMin,xMax)localxifnotxTypeorxType=="number"thenlocalisIntegerx,isInteger=numericArray(serializedX)ifxthenxMin=tonumber(xMin)xMax=tonumber(xMax)ifnotxTypethenifisIntegerthenxType="number"endendelseifxTypethenerror("Numbers expected for parameter 'x'")endendendifnotxthenx=stringArray(serializedX)ifnotxTypethenxType="string"endendreturnx,xType,xMin,xMaxendlocalfunctiondeserializeYData(serializedYs,yType,yMin,yMax)localy={}localareAllInteger=trueforyNum,valueinpairs(serializedYs)dolocalyValuesifnotyTypeoryType=="number"thenlocalisIntegeryValues,isInteger=numericArray(value)ifyValuesthenareAllInteger=areAllIntegerandisIntegerelseifyTypethenerror("Numbers expected for parameter '"..name.."'")elsereturndeserializeYData(serializedYs,"string",yMin,yMax)endendendifnotyValuesthenyValues=stringArray(value)endy[yNum]=yValuesendifnotyTypethenifareAllIntegerthenyType="number"endendifyType=="number"thenyMin=tonumber(yMin)yMax=tonumber(yMax)endreturny,yType,yMin,yMaxendlocalfunctionconvertXYToManySeries(x,y,xType,yType,seriesTitles)localdata={name="chart",format={type="json",parse={x=xType,y=yType}},values={},transform={}}fori=1,#ydolocalyLen=table.maxn(y[i])forj=1,#xdoifj<=yLenandy[i][j]thentable.insert(data.values,{series=seriesTitles[i],index=i,x=x[j],y=y[i][j]})endendendreturndataendlocalfunctionconvertXYToSingleSeries(x,y,xType,yType,yNames)localdata={name="chart",format={type="json",parse={x=xType}},values={},transform={}}forj=1,#ydodata.format.parse[yNames[j]]=yTypeendfori=1,#xdolocalitem={x=x[i]}forj=1,#ydoitem[yNames[j]]=y[j][i]endtable.insert(data.values,item)endreturndataendlocalfunctiongetXScale(chartType,stacked,xMin,xMax,xType,xScaleType)ifchartType=="pie"thenreturnendlocalxscale={name="x",range="width",zero=false,-- do not include zero valuenice=true,domain={data="chart",field="x"}}ifxScaleTypethenxscale.type=xScaleTypeelsexscale.type="linear"endifxMinthenxscale.domainMin=tonumber(xMin)endifxMaxthenxscale.domainMax=tonumber(xMax)endifxMinorxMaxthenxscale.clamp=truexscale.nice=falseendifchartType=="rect"thenxscale.type="band"xscale.zero=nilxscale.nice=nilifnotstackedthenxscale.padding=0.2end-- pad each bar groupelseifxType=="date"thenxscale.type="time"elseifxType=="string"thenxscale.type="ordinal"xscale.points=trueendendifxTypeandxType~="date"andxScaleType~="log"thenxscale.nice=trueend-- force round numbers for x scale, but "log" and "date" scale outputs a wrong "nice" scalereturnxscaleendlocalfunctiongetYScale(chartType,stacked,yMin,yMax,yType,yScaleType)ifchartType=="pie"thenreturnendlocalyscale={name="y",range="height",-- area charts have the lower boundary of their filling at y=0 (see marks.encode.enter.y2), therefore these need to start at zerozero=chartType~="line",nice=true,}ifyScaleTypethenyscale.type=yScaleTypeelseyscale.type="linear"endifyMinthenyscale.domainMin=tonumber(yMin)endifyMaxthenyscale.domainMax=tonumber(yMax)endifyMinoryMaxthenyscale.clamp=trueendifyType=="date"thenyscale.type="time"elseifyType=="string"thenyscale.type="ordinal"endifstackedthenyscale.domain={data="chart",field="y1"}elseyscale.domain={data="chart",field="y"}endreturnyscaleendlocalfunctiongetColorScale(colors,chartType,xCount,yCount)ifnotcolorsthencolors={scheme="category10"}if(chartType=="pie"andxCount>10)oryCount>10thencolors={scheme="category20"}elsecolors={scheme="category10"}endendlocalcolorScale={name="color",type="ordinal",range=colors,domain={data="chart",field="series"}}ifchartType=="pie"thencolorScale.domain.field="x"endreturncolorScaleendlocalfunctiongetAlphaColorScale(colors,y)localalphaScale-- if there is at least one color in the format "#aarrggbb", create a transparency (alpha) scaleifisTable(colors)thenlocalalphas={}localhasAlpha=falsefori=1,#colorsdolocala,rgb=string.match(colors[i],"#(%x%x)(%x%x%x%x%x%x)")ifathenhasAlpha=truealphas[i]=tostring(tonumber(a,16)/255.0)colors[i]="#"..rgbelsealphas[i]="1"endendfori=#colors+1,#ydoalphas[i]="1"endifhasAlphathenalphaScale={name="transparency",type="ordinal",range=alphas}endendreturnalphaScaleendlocalfunctiongetLineScale(linewidths,chartType)locallineScale={}lineScale={name="line",type="ordinal",range=linewidths,domain={data="chart",field="series"}}returnlineScaleendlocalfunctiongetSymSizeScale(symSize)localSymSizeScale={}SymSizeScale={name="symSize",type="ordinal",range=symSize,domain={data="chart",field="series"}}returnSymSizeScaleendlocalfunctiongetSymShapeScale(symShape)localSymShapeScale={}SymShapeScale={name="symShape",type="ordinal",range=symShape,domain={data="chart",field="series"}}returnSymShapeScaleendlocalfunctiongetValueScale(fieldName,min,max,type)localvalueScale={name=fieldName,type=typeor"linear",domain={data="chart",field=fieldName},range={min,max}}returnvalueScaleendlocalfunctionaddInteractionToChartVisualisation(plotMarks,colorField,dataField)-- initial setupifnotplotMarks.encode.enterthenplotMarks.encode.enter={}endplotMarks.encode.enter[colorField]={scale="color",field=dataField}-- action when cursor is over plot mark: highlightifnotplotMarks.encode.hoverthenplotMarks.encode.hover={}endplotMarks.encode.hover[colorField]={value="red"}-- action when cursor leaves plot mark: reset to initial setupifnotplotMarks.encode.updatethenplotMarks.encode.update={}endplotMarks.encode.update[colorField]={scale="color",field=dataField}endlocalfunctiongetPieChartVisualisation(yCount,innerRadius,outerRadius,linewidth,radiusScale,graphwidth,graphheight)localchartvis={type="arc",from={data="chart"},encode={enter={x={value=graphwidth/2},y={value=graphheight/2}},update={innerRadius={value=innerRadius},outerRadius={value=outerRadius},startAngle={field="startAngle"},endAngle={field="endAngle"},stroke={value="white"},strokeWidth={value=linewidthor1}}}}ifradiusScalethenchartvis.encode.update.outerRadius.scale=radiusScale.namechartvis.encode.update.outerRadius.field=radiusScale.domain.fieldelsechartvis.encode.update.outerRadius.value=outerRadiusendaddInteractionToChartVisualisation(chartvis,"fill","x")returnchartvisendlocalfunctiongetChartVisualisation(chartType,stacked,colorField,yCount,innerRadius,outerRadius,linewidth,alphaScale,radiusScale,lineScale,interpolate,graphwidth,graphheight)ifchartType=="pie"thenreturngetPieChartVisualisation(yCount,innerRadius,outerRadius,linewidth,radiusScale,graphwidth,graphheight)endlocalchartvis={type=chartType,encode={-- chart creation event handlerenter={x={scale="x",field="x"},y={scale="y",field="y"}}}}addInteractionToChartVisualisation(chartvis,colorField,"series")ifcolorField=="stroke"thenchartvis.encode.enter.strokeWidth={value=linewidthor2.5}iftype(lineScale)=="table"thenchartvis.encode.enter.strokeWidth.value=nilchartvis.encode.enter.strokeWidth={scale="line",field="series"}endendifinterpolatethenchartvis.encode.enter.interpolate={value=interpolate}endifalphaScalethenchartvis.encode.update[colorField.."Opacity"]={scale="transparency"}end-- for bars and area charts set the lower bound of their areasifchartType=="rect"orchartType=="area"thenifstackedthen-- for stacked charts this lower bound is the end of the last stacking elementchartvis.encode.enter.y2={scale="y",field="layout_end"}else--[[ for non-stacking charts the lower bound is y=0 TODO: "yscale.zero" is currently set to "true" for this case, but "false" for all other cases. For the similar behavior "y2" should actually be set to where y axis crosses the x axis, if there are only positive or negative values in the data ]]chartvis.encode.enter.y2={scale="y",value=0}endend-- for bar charts ...ifchartType=="rect"then-- set 1 pixel width between the barschartvis.encode.enter.width={scale="x",band=true,offset=-1}-- for multiple series the bar marking needs to use the "inner" series scale, whereas the "outer" x scale is used by the groupingifnotstackedandyCount>1thenchartvis.encode.enter.x.scale="x"chartvis.encode.enter.x.field="x"chartvis.encode.enter.width.scale="series"endend-- stacked charts have their own (stacked) y valuesifstackedthenchartvis.encode.enter.y.field="layout_start"end-- if there are multiple series group these togetherifyCount==1thenchartvis.from={data="chart"}elsechartvis.from={data="facet"}-- if there are multiple series, connect colors to serieschartvis.encode.enter[colorField].field="series"ifalphaScalethenchartvis.encode.update[colorField.."Opacity"].field="series"end---- TODO check? if there are multiple series, connect linewidths to series-- if chartType == "line" then-- chartvis.encode.update.strokeWidth.field = "series"--end-- apply a grouping (facetting) transformationchartvis={type="group",from={facet={data="chart",name="facet",groupby="series"}},marks=chartvis}-- for stacked charts apply a stacking transformationifstackedthen-- TODO must check for non bar--table.insert(chartvis.from.transform, 1, { type = "stack", groupby = { "x" }, sortby = { "-_id" }, field = "y" } )-- transform goes to data in vega 5chartvis.marks.encode.enter.y={scale="y";field="y1"}chartvis.marks.encode.enter.y2={scale="y";field="y0"}chartvis.marks={chartvis.marks}else--for bar charts the series are side-by-side grouped by xifchartType=="rect"then-- for bar charts with multiple series: each serie is grouped by the x value, therefore the series need their own scale within each x groupchartvis.from.facet.groupby="x"chartvis.signals={{name="width",update="bandwidth('x')"}}-- calculation or width for each group od barschartvis.scales={{name="facet_index",type="band",range="width",domain={data="facet",field="series"}}}chartvis.encode={enter={x={scale="x",field="x"}}}chartvis.marks.encode.enter.x={field="series",scale="facet_index"}chartvis.marks.encode.enter.width={scale="facet_index",band=true}chartvis.marks={chartvis.marks}elsechartvis.marks={chartvis.marks}endendendreturnchartvisendlocalfunctiongetTextMarks(chartvis,chartType,outerRadius,scales,radiusScale,yType,showValues,graphwidth,graphheight)localencodeifchartType=="rect"thenencode={x={scale=chartvis.encode.enter.x.scale,field=chartvis.encode.enter.x.field},y={scale=chartvis.encode.enter.y.scale,field=chartvis.encode.enter.y.field,offset=-(tonumber(showValues.offset)or-4)},--dx = { scale = chartvis.encode.enter.x.scale, band = true, mult = 0.5 }, -- for horizontal textdy={scale=chartvis.encode.enter.x.scale,band=true,mult=0.5},-- for vertical textalign={},baseline={value="middle"},fill={},angle={value=-90},fontSize={value=tonumber(showValues.fontsize)or11}}ifencode.y.offset>=0thenencode.align.value="right"encode.fill.value=showValues.fontcoloror"white"elseencode.align.value="left"encode.fill.value=showValues.fontcolororpersistentGreyendelseifchartType=="pie"thenencode={x={value=graphwidth/2},y={value=graphheight/2},radius={offset=tonumber(showValues.offset)or-15},theta={signal="(datum.startAngle + datum.endAngle)/2"},fill={value=showValues.fontcolororpersistentGrey},baseline={},angle={},fontSize={value=tonumber(showValues.fontsize)ormath.ceil(outerRadius/10)}}if(showValues.angleor"midangle")=="midangle"then-- TODO check always true ?encode.align={value="center"}encode.angle={field="layout_mid",mult=180.0/math.pi}ifencode.radius.offset>=0thenencode.baseline.value="bottom"elseifnotshowValues.fontcolorthenencode.fill.value="white"endencode.baseline.value="middle"endelseiftonumber(showValues.angle)then-- Todo check-- qunatize scale for aligning text left on right half-circle and right on left half-circlelocalalignScale={name="align",type="quantize",domainMin=0.0,domainMax=math.pi*2,range={"left","right"}}table.insert(scales,alignScale)encode.align={scale=alignScale.name,field="layout_mid"}encode.angle={value=tonumber(showValues.angle)}encode.baseline.value="middle"ifnottonumber(showValues.offset)thenencode.radius.offset=4endendifradiusScalethenencode.radius.scale=radiusScale.nameencode.radius.field=radiusScale.domain.fieldelseencode.radius.value=outerRadiusendendifencodethenifshowValues.formatthenlocaltemplate="datum.y"ifyType=="number"thentemplate=template.."|number:'"..showValues.format.."'"elseifyType=="date"thentemplate=template.."|time:"..showValues.format.."'"endencode.text={template="{{"..template.."}}"}elseencode.text={field="y"}endlocaltextmarks={type="text",encode={enter=encode}}ifchartvis.fromthentextmarks.from=copy(chartvis.from)endreturntextmarksendendlocalfunctiongetSymbolMarks(chartvis,symSize,symShape,symStroke,noFill,alphaScale)localsymbolmarkssymbolmarks={type="symbol",encode={enter={x={scale="x",field="x"},y={scale="y",field="y"},strokeWidth={value=symStroke},stroke={scale="color",field="series"},fill={scale="color",field="series"},}}}iftype(symShape)=="string"thensymbolmarks.encode.enter.shape={value=symShape}endiftype(symShape)=="table"thensymbolmarks.encode.enter.shape={scale="symShape",field="series"}endiftype(symSize)=="number"thensymbolmarks.encode.enter.size={value=symSize}endiftype(symSize)=="table"thensymbolmarks.encode.enter.size={scale="symSize",field="series"}endifnoFillthensymbolmarks.encode.enter.fill=nilendifalphaScalethensymbolmarks.encode.enter.fillOpacity={scale="transparency",field="series"}symbolmarks.encode.enter.strokeOpacity={scale="transparency",field="series"}endifchartvis.fromthensymbolmarks.from=copy(chartvis.from)endreturnsymbolmarksendlocalfunctiongetAnnoMarks(chartvis,stroke,fill,opacity)localvannolines,hannolines,vannolabels,hannolabelsvannolines={type="rule",from={data="v_anno"},encode={update={x={scale="x",field="x"},y={value=0},y2={field={group="height"}},strokeWidth={value=stroke},stroke={value=persistentGrey},opacity={value=opacity}}}}vannolabels={type="text",from={data="v_anno"},encode={update={x={scale="x",field="x",offset=3},y={field={group="height"},offset=-3},text={field="label"},baseline={value="top"},angle={value=-90},fill={value=persistentGrey},opacity={value=opacity}}}}hannolines={type="rule",from={data="h_anno"},encode={update={y={scale="y",field="y"},x={value=0},x2={field={group="width"}},strokeWidth={value=stroke},stroke={value=persistentGrey},opacity={value=opacity}}}}hannolabels={type="text",from={data="h_anno"},encode={update={y={scale="y",field="y",offset=3},x={value=0,offset=3},text={field="label"},baseline={value="top"},angle={value=0},fill={value=persistentGrey},opacity={value=opacity}}}}returnvannolines,vannolabels,hannolines,hannolabelsendlocalfunctiongetAxes(xTitle,xAxisFormat,xAxisAngle,xType,xGrid,yTitle,yAxisFormat,yType,yGrid,chartType,graphheight)localxAxis,yAxisifchartType~="pie"thenifxType=="number"andnotxAxisFormatthenxAxisFormat="d"endxAxis={scale="x",title=xTitle,format=xAxisFormat,grid=xGrid,-- hard coding required orient valuesorient="bottom"}ifxAxisFormat=="d"thenxAxis.tickMinStep=1endifxAxisAnglethenlocalxAxisAlignifxAxisAngle<0thenxAxisAlign="right"elsexAxisAlign="left"endxAxis.encode={title={fill={value=persistentGrey}},labels={angle={value=xAxisAngle},align={value=xAxisAlign},fill={value=persistentGrey}},ticks={stroke={value=persistentGrey}},axis={stroke={value=persistentGrey},strokeWidth={value=2}},grid={stroke={value=persistentGrey}}}elsexAxis.encode={title={fill={value=persistentGrey}},labels={fill={value=persistentGrey}},ticks={stroke={value=persistentGrey}},axis={stroke={value=persistentGrey},strokeWidth={value=2}},grid={stroke={value=persistentGrey}}}endifyType=="number"andnotyAxisFormatthenyAxisFormat="d"endyAxis={scale="y",title=yTitle,format=yAxisFormat,grid=yGrid,-- hard coding required orient valuesorient="left"}ifyAxisFormat=="d"thenyAxis.tickMinStep=1ifgraphheight<151thenyAxis.tickCount=math.max(math.floor(graphheight/12),2)endendyAxis.encode={title={fill={value=persistentGrey}},labels={fill={value=persistentGrey}},ticks={stroke={value=persistentGrey}},axis={stroke={value=persistentGrey},strokeWidth={value=2}},grid={stroke={value=persistentGrey}}}endreturnxAxis,yAxisendlocalfunctiongetLegend(legendTitle,chartType,outerRadius)locallegend={fill="color",stroke="color",title=legendTitle,}legend.titleColor=persistentGreylegend.labelColor=persistentGreyifchartType=="pie"thenlegend.orient="top-right"legend.titleColor=persistentGreylegend.labelColor=persistentGreyendreturnlegendendfunctionp.map(frame)-- map path data for geographic objectslocalbasemap=frame.args.basemapor"WorldMap-iso2.json"-- WorldMap name and/or location may vary from wiki to wiki-- scaling factorlocalscale=tonumber(frame.args.scale)or60-- map projection, see https://github.com/mbostock/d3/wiki/Geo-Projectionslocalprojection=frame.args.projectionor"equirectangular"-- defaultValue for geographic objects without datalocaldefaultValue=frame.args.defaultValueorframe.args.defaultvaluelocalscaleType=frame.args.scaleTypeorframe.args.scaletype-- minimaler Wertebereich (nur für numerische Daten)localdomainMin=tonumber(frame.args.domainMinorframe.args.domainmin)-- maximaler Wertebereich (nur für numerische Daten)localdomainMax=tonumber(frame.args.domainMaxorframe.args.domainmax)-- Farbwerte der Farbskala (nur für numerische Daten)localcolorScale=frame.args.colorScaleorframe.args.colorscaleor"category10"-- show legendlocallegend=frame.args.legend-- the map feature to displaylocalfeature=frame.args.featureor"countries"-- map centerlocalcenter=numericArray(frame.args.center)-- format JSON outputlocalformatJson=frame.args.formatjson-- map data are key-value pairs: keys are non-lowercase strings (ideally ISO codes) which need to match the "id" values of the map path datalocalvalues={}localisNumbers=nilforname,valueinpairs(frame.args)doifmw.ustring.find(name,"^[^%l]+$")andvalueandvalue~=""thenifisNumbers==nilthenisNumbers=tonumber(value)endlocaldata={id=name,v=value}ifisNumbersthendata.v=tonumber(data.v)endtable.insert(values,data)endendifnotdefaultValuethenifisNumbersthendefaultValue=0elsedefaultValue="silver"endend-- create highlight scalelocalscalesifisNumbersthenifcolorScalethencolorScale=string.lower(colorScale)endifcolorScale=="category10"orcolorScale=="category20"thencolorScale={scheme=colorScale}elsecolorScale=stringArray(colorScale)endscales={{name="color",type=scaleTypeor"linear",domain={data="highlights",field="v"},range=colorScale,-- nice = true,zero=false}}ifdomainMinthenscales[1].domainMin=domainMinendifdomainMaxthenscales[1].domainMax=domainMaxendlocalexponent=string.match(scaleType,"pow%s+(%d+%.?%d+)")-- check for exponentifexponentthenscales[1].type="pow"scales[1].exponent=exponentendelseifcolorScale=="category10"orcolorScale=="category20"thencolorScale={scheme=colorScale}elsecolorScale=stringArray(colorScale)endscales={{name="color",type=scaleTypeor"ordinal",domain={data="highlights",field="v"},range=colorScale,}}end-- create legendiflegendthenlegend={fill="color",stroke="color",title=legendTitle,}legend.titleColor=persistentGreylegend.labelColor=persistentGreyend-- get map urllocalbasemapUrlif(string.sub(basemap,1,10)=="wikiraw://")thenbasemapUrl=basemapelse-- if not a (supported) url look for a colon as namespace separator. If none prepend default map directory name.ifnotstring.find(basemap,":")thenbasemap=baseMapDirectory..basemapendbasemapUrl="wikiraw:///"..mw.uri.encode(mw.title.new(basemap).prefixedText,"PATH")endlocaloutput={schema="https://vega.github.io/schema/vega/v5.json",width=190,height=60,-- fit nicely world map with scale 60projections={{name="projection",value="data",-- data sourcescale=scale,translate={0,0},center=center,type=projection},},data={{-- data source for the highlightsname="highlights",values=values},{-- data source for map paths dataname=feature,url=basemapUrl,format={type="topojson",feature=feature},transform={{-- join ("zip") of mutiple data source: here map paths data and highlightstype="lookup",fields={"id"},-- key for map paths datafrom="highlights",-- name of highlight data sourcekey="id",-- key for highlight data sourceas={"zipped"},-- name of resulting tabledefault={v=defaultValue}-- default value for geographic objects that could not be joined}}}},marks={-- output markings (map paths and highlights){transform={{type="geoshape",projection="projection"}},type="shape",from={data=feature},encode={enter={stroke={value=persistentGrey}},update={fill={field="zipped.v"}},hover={fill={value=persistentGrey}}}}},legends={legend}}if(scales)thenoutput.scales=scalesoutput.marks[1].encode.update.fill.scale="color"endlocalflagsifformatJsonthenflags=mw.text.JSON_PRETTYendJSONtemp=mw.text.jsonEncode(output,flags)-- $ is not allowed in variable name so it need to be added in JSON string JSON=string.gsub(JSONtemp,'\"schema\":\"https://vega.github.io/schema/vega/v5.json\"','\"$schema\":\"https://vega.github.io/schema/vega/v5.json\"',1)returnJSONendfunctionp.chart(frame)-- chart width and heightlocalgraphwidth=tonumber(frame.args.width)or200localgraphheight=tonumber(frame.args.height)or200-- chart typelocalchartType=frame.args.typeor"line"-- interpolation mode for line and area charts: linear, step-before, step-after, basis, basis-open, basis-closed (type=line only), bundle (type=line only), cardinal, cardinal-open, cardinal-closed (type=line only), monotonelocalinterpolate=frame.args.interpolate-- mark colors (if no colors are given, the default 10 color palette is used)localcolorString=frame.args.colorsifcolorStringthencolorString=string.lower(colorString)endlocalcolors=stringArray(colorString)-- for line charts, the thickness of the line; for pie charts the gap between each slicelocallinewidth=tonumber(frame.args.linewidth)locallinewidthsString=frame.args.linewidthslocallinewidthsiflinewidthsStringandlinewidthsString~=""thenlinewidths=numericArray(linewidthsString)orfalseend-- x and y axis captionlocalxTitle=frame.args.xAxisTitleorframe.args.xaxistitlelocalyTitle=frame.args.yAxisTitleorframe.args.yaxistitle-- x and y value typeslocalxType=frame.args.xTypeorframe.args.xtypelocalyType=frame.args.yTypeorframe.args.ytype-- override x and y axis minimum and maximumlocalxMin=frame.args.xAxisMinorframe.args.xaxisminlocalxMax=frame.args.xAxisMaxorframe.args.xaxismaxlocalyMin=frame.args.yAxisMinorframe.args.yaxisminlocalyMax=frame.args.yAxisMaxorframe.args.yaxismax-- override x and y axis label formattinglocalxAxisFormat=frame.args.xAxisFormatorframe.args.xaxisformatlocalyAxisFormat=frame.args.yAxisFormatorframe.args.yaxisformatlocalxAxisAngle=tonumber(frame.args.xAxisAngle)ortonumber(frame.args.xaxisangle)-- x and y scale typeslocalxScaleType=frame.args.xScaleTypeorframe.args.xscaletypelocalyScaleType=frame.args.yScaleTypeorframe.args.yscaletype-- log scale require minimum > 0, for now it's no possible to plot negative values on log - TODO see: https://www.mathworks.com/matlabcentral/answers/1792-log-scale-graphic-with-negative-value-- if xScaleType == "log" then-- if (not xMin or tonumber(xMin) <= 0) then xMin = 0.1 end-- if not xType then xType = "number" end-- end-- if yScaleType == "log" then-- if (not yMin or tonumber(yMin) <= 0) then yMin = 0.1 end-- if not yType then yType = "number" end-- end-- show gridlocalxGrid=frame.args.xGridorframe.args.xgridorfalselocalyGrid=frame.args.yGridorframe.args.ygridorfalse-- grids fail-safeifxGridthenifxGrid=="0"thenxGrid=falseelseifxGrid==0thenxGrid=falseelseifxGrid=="false"thenxGrid=falseelseifxGrid=="n"thenxGrid=falseelsexGrid=trueendendifyGridthenifyGrid=="0"thenyGrid=falseelseifyGrid==0thenyGrid=falseelseifyGrid=="false"thenyGrid=falseelseifyGrid=="n"thenyGrid=falseelseyGrid=trueendend-- for line chart, show a symbol at each data pointlocalshowSymbols=frame.args.showSymbolsorframe.args.showsymbolslocalsymbolsShape=frame.args.symbolsShapeorframe.args.symbolsshapelocalsymbolsNoFill=frame.args.symbolsNoFillorframe.args.symbolsnofilllocalsymbolsStroke=tonumber(frame.args.symbolsStrokeorframe.args.symbolsstroke)-- show legend with given titlelocallegendTitle=frame.args.legend-- show values as textlocalshowValues=frame.args.showValuesorframe.args.showvalues-- show v- and h-line annotationslocalv_annoLineString=frame.args.vAnnotatonsLineorframe.args.vannotatonslinelocalh_annoLineString=frame.args.hAnnotatonsLineorframe.args.hannotatonslinelocalv_annoLabelString=frame.args.vAnnotatonsLabelorframe.args.vannotatonslabellocalh_annoLabelString=frame.args.hAnnotatonsLabelorframe.args.hannotatonslabel-- decode annotations cvslocalv_annoLine,v_annoLabel,h_annoLine,h_annoLabelifv_annoLineStringandv_annoLineString~=""thenifxType=="number"thenv_annoLine=numericArray(v_annoLineString)elsev_annoLine=stringArray(v_annoLineString)endv_annoLabel=stringArray(v_annoLabelString)endifh_annoLineStringandh_annoLineString~=""thenifyType=="number"thenh_annoLine=numericArray(h_annoLineString)elseh_annoLine=stringArray(h_annoLineString)endh_annoLabel=stringArray(h_annoLabelString)end-- pie chart radiuseslocalinnerRadius=tonumber(frame.args.innerRadius)ortonumber(frame.args.innerradius)or0localouterRadius=math.min(graphwidth,graphheight)/2;-- format JSON outputlocalformatJson=frame.args.formatjson-- get x valueslocalxx,xType,xMin,xMax=deserializeXData(frame.args.x,xType,xMin,xMax)-- get y values (series)localyValues={}localseriesTitles={}forname,valueinpairs(frame.args)dolocalyNumifname=="y"thenyNum=1elseyNum=tonumber(string.match(name,"^y(%d+)$"))endifyNumthenyValues[yNum]=value-- name the series: default is "y<number>". Can be overwritten using the "y<number>Title" parameters.seriesTitles[yNum]=frame.args["y"..yNum.."Title"]orframe.args["y"..yNum.."title"]ornameendendlocalyy,yType,yMin,yMax=deserializeYData(yValues,yType,yMin,yMax)-- create data tuples, consisting of series index, x value, y valuelocaldata,transformifchartType=="pie"then-- for pie charts the second second series is merged into the first series as radius valuesdata=convertXYToSingleSeries(x,y,xType,yType,{"y","r"})elsedata=convertXYToManySeries(x,y,xType,yType,seriesTitles)end-- configure transform in data for stacked charts and pielocalstacked=falseifstring.sub(chartType,1,7)=="stacked"thenchartType=string.sub(chartType,8)if#y>1then-- ignore stacked charts if there is only one seriesstacked=true-- aggregate data by cumulative y valuestransform={{type="stack",groupby={"x"},sort={field="index"},field="y"}}elsetransform={}endendifchartType=="pie"thentransform={{field="y",type="pie"}}end-- add annotations to datalocalvannoData,hannoDataifv_annoLinethenvannoData={name="v_anno",format={type="json",parse={x=xType}},values={}}fori=1,#v_annoLinedolocalitem={x=v_annoLine[i],label=v_annoLabel[i]}table.insert(vannoData.values,item)endendifh_annoLinethenhannoData={name="h_anno",format={type="json",parse={y=yType}},values={}}fori=1,#h_annoLinedolocalitem={y=h_annoLine[i],label=h_annoLabel[i]}table.insert(hannoData.values,item)endend-- create scaleslocalscales={}localxscale=getXScale(chartType,stacked,xMin,xMax,xType,xScaleType)table.insert(scales,xscale)localyscale=getYScale(chartType,stacked,yMin,yMax,yType,yScaleType)table.insert(scales,yscale)localcolorScale=getColorScale(colors,chartType,#x,#y)table.insert(scales,colorScale)localalphaScale=getAlphaColorScale(colors,y)table.insert(scales,alphaScale)locallineScaleif(linewidths)and(chartType=="line")thenlineScale=getLineScale(linewidths,chartType)table.insert(scales,lineScale)endlocalradiusScaleifchartType=="pie"and#y>1thenradiusScale=getValueScale("r",0,outerRadius)table.insert(scales,radiusScale)end-- decide if lines (strokes) or areas (fills) should be drawnlocalcolorFieldifchartType=="line"thencolorField="stroke"elsecolorField="fill"end-- create chart markingslocalchartvis=getChartVisualisation(chartType,stacked,colorField,#y,innerRadius,outerRadius,linewidth,alphaScale,radiusScale,lineScale,interpolate,graphwidth,graphheight)localmarks={chartvis}-- text marksifshowValuestheniftype(showValues)=="string"then-- deserialize as tablelocalkeyValues=mw.text.split(showValues,"%s*,%s*")showValues={}for_,kvinipairs(keyValues)dolocalkey,value=mw.ustring.match(kv,"^%s*(.-)%s*:%s*(.-)%s*$")ifkeythenshowValues[key]=valueendendendlocalchartmarks=chartvisifchartmarks.marksthenchartmarks=chartmarks.marks[1]endlocaltextmarks=getTextMarks(chartmarks,chartType,outerRadius,scales,radiusScale,yType,showValues,graphwidth,graphheight)ifchartmarks~=chartvisthentable.insert(chartvis.marks,textmarks)elsetable.insert(marks,textmarks)endend-- symbol marksifshowSymbolsandchartType~="rect"thenlocalchartmarks=chartvisifchartmarks.marksthenchartmarks=chartmarks.marks[1]endiftype(showSymbols)=="string"thenifshowSymbols==""thenshowSymbols=trueelseshowSymbols=numericArray(showSymbols)endelseshowSymbols=tonumber(showSymbols)end-- custom symbol sizelocalsymSizeiftype(showSymbols)=="number"thensymSize=tonumber(showSymbols*showSymbols*8.5)elseiftype(showSymbols)=="table"thensymSize={}fork,vinpairs(showSymbols)dosymSize[k]=v*v*8.5-- "size" acc to Vega syntax is area of symbolendelsesymSize=50end-- symSizeScale localsymSizeScale={}iftype(symSize)=="table"thensymSizeScale=getSymSizeScale(symSize)table.insert(scales,symSizeScale)end-- custom shapeifstringArray(symbolsShape)and#stringArray(symbolsShape)>1thensymbolsShape=stringArray(symbolsShape)endlocalsymShape--= " "iftype(symbolsShape)=="string"andshapes[symbolsShape]thensymShape=shapes[symbolsShape]elseiftype(symbolsShape)=="table"thensymShape={}fork,vinpairs(symbolsShape)doifsymbolsShape[k]andshapes[symbolsShape[k]]thensymShape[k]=shapes[symbolsShape[k]]elsesymShape[k]="circle"endendelsesymShape="circle"end-- symShapeScale localsymShapeScale={}iftype(symShape)=="table"thensymShapeScale=getSymShapeScale(symShape)table.insert(scales,symShapeScale)end-- custom strokelocalsymStrokeif(type(symbolsStroke)=="number")thensymStroke=tonumber(symbolsStroke)-- TODO symStroke serialization-- elseif type(symbolsStroke) == "table" then -- symStroke = {}-- for k, v in pairs(symbolsStroke) do-- symStroke[k]=symbolsStroke[k]-- --always draw x with stroke-- if symbolsShape[k] == "x" then symStroke[k] = 2.5 end--always draw x with stroke-- if symbolsNoFill[k] then symStroke[k] = 2.5 end-- endelsesymStroke=0--always draw x with strokeifsymbolsShape=="x"thensymStroke=2.5end--always draw x with strokeifsymbolsNoFillthensymStroke=2.5endend-- TODO -- symStrokeScale -- local symStrokeScale = {}-- if type(symStroke) == "table" then-- symStrokeScale = getSymStrokeScale(symStroke)-- table.insert(scales, symStrokeScale)-- endlocalsymbolmarks=getSymbolMarks(chartmarks,symSize,symShape,symStroke,symbolsNoFill,alphaScale)ifchartmarks~=chartvisthentable.insert(chartvis.marks,symbolmarks)elsetable.insert(marks,symbolmarks)endendlocalvannolines,vannolabels,hannolines,hannolabels=getAnnoMarks(chartmarks,2.5,persistentGrey,0.75)ifvannoDatathentable.insert(marks,vannolines)table.insert(marks,vannolabels)endifhannoDatathentable.insert(marks,hannolines)table.insert(marks,hannolabels)end-- axeslocalxAxis,yAxis=getAxes(xTitle,xAxisFormat,xAxisAngle,xType,xGrid,yTitle,yAxisFormat,yType,yGrid,chartType,graphheight)-- legendlocallegendiflegendTitleandtonumber(legendTitle)~=0thenlegend=getLegend(legendTitle,chartType,outerRadius)endiflegendandchartType=="pie"andouterRadius<graphwidth/2+100thengraphwidth=graphwidth+100end-- construct final output objectlocaloutput={schema="https://vega.github.io/schema/vega/v5.json",width=graphwidth,height=graphheight,data={data},scales=scales,axes={xAxis,yAxis},marks=marks,legends={legend}}ifvannoDatathentable.insert(output.data,vannoData)endifhannoDatathentable.insert(output.data,hannoData)endiftransformthendata.transform=transformend-- table.insert(output.data.transform, transform) endlocalflagsifformatJsonthenflags=mw.text.JSON_PRETTYendJSONtemp=mw.text.jsonEncode(output,flags)-- $ is not allowed in variable name so it need to be added in JSON string JSON=string.gsub(JSONtemp,'\"schema\":\"https://vega.github.io/schema/vega/v5.json\"','\"$schema\":\"https://vega.github.io/schema/vega/v5.json\"',1)returnJSONendfunctionp.mapWrapper(frame)returnp.map(frame:getParent())endfunctionp.chartWrapper(frame)returnp.chart(frame:getParent())endfunctionp.chartDebuger(frame)return"\n\nchart JSON\n "..p.chart(frame).." \n\n"..debuglogend-- Given an HTML-encoded title as first argument, e.g. one produced with {{ARTICLEPAGENAME}},-- convert it into a properly URL path-encoded string-- This function is critical for any graph that uses path-based APIs, e.g. PageViews graphfunctionp.encodeTitleForPath(frame)returnmw.uri.encode(mw.text.decode(mw.text.trim(frame.args[1])),'PATH')endreturnp-- BUGS: [check if still exist in Vega 5] -- X-Axis label format bug? (xAxisFormat =) https://en.wikipedia.org/wiki/Template_talk:Graph:Chart#X-Axis_label_format_bug?_(xAxisFormat_=)-- linewidths - doesnt work for two values (eg 0, 1) but work if added third value of both are zeros? Same for marksStroke - probably bug in Graph extension-- Reordering even strings like integers - see https://en.wikipedia.org/wiki/Template_talk:Graph:Chart#Reordering_even_strings_like_integers-- TODO: -- - bugs from Vega 2 - check if still exist in Vega 5-- - marks:-- - line strokeDash + serialization,-- - symStroke serialization-- - symbolsNoFill serialization-- - arbitrary SVG path symbol shape as symbolsShape argument-- - annotations-- - rectangle shape for x,y data range-- - graph type serialization (deep rebuild reqired)-- - second axis (deep rebuild required - assignment of series to one of two axies) -- Version History (_PLEASE UPDATE when modifying anything_):-- 2023-09-10 Update to Vega 5 (except maps)-- 2020-09-01 Vertical and horizontal line annotations-- 2020-08-08 New logic for "nice" for x axis (problem with scale when xType = "date") and grid-- 2020-06-21 Serializes symbol size-- transparent symbosls (from line colour) - buggy (incorrect opacity on overlap with line)-- Linewidth serialized with "linewidths"-- Variable symbol size and shape of symbols on line charts, default showSymbols = 2, default symbolsShape = circle, symbolsStroke = 0-- p.chartDebuger(frame) for easy debug and JSON output -- 2020-06-07 Allow lowercase variables for use with [[Template:Wikidata list]]-- 2020-05-27 Map: allow specification which feature to display and changing the map center-- 2020-04-08 Change default showValues.fontcolor from black to persistentGrey-- 2020-04-06 Logarithmic scale outputs wrong axis labels when "nice"=true-- 2020-03-11 Allow user-defined scale types, e.g. logarithmic scale-- 2019-11-08 Apply color-inversion-friendliness to legend title, labels, and xGrid-- 2019-01-24 Allow comma-separated lists to contain values with commas-- 2018-10-13 Fix browser color-inversion issues via #54595d per [[mw:Template:Graph:PageViews]]-- 2018-09-16 Allow disabling the legend for templates-- 2018-09-10 Allow grid lines-- 2018-08-26 Use user-defined order for stacked charts-- 2018-02-11 Force usage of explicitely provided x minimum and/or maximum values, rotation of x labels-- 2017-08-08 Added showSymbols param to show symbols on line charts-- 2016-05-16 Added encodeTitleForPath() to help all path-based APIs graphs like pageviews-- 2016-03-20 Allow omitted data for charts, labels for line charts with string (ordinal) scale at point location-- 2016-01-28 For maps, always use wikiraw:// protocol. https:// will be disabled soon.
close