Anda dapat membuat halaman dokumentasi untuk buat pada modul Scribunto ini.
Penyunting dapat melakukan uji coba pada halaman bak pasir (buat | cermin) dan kasus uji (buat) modul ini.
Subhalaman modul ini.
Penyunting dapat melakukan uji coba pada halaman bak pasir (buat | cermin) dan kasus uji (buat) modul ini.
Subhalaman modul ini.
-- ATTENTION: Please edit this code at https://de.wikipedia.org/wiki/Modul:Graph -- This way all wiki languages can stay in sync. Thank you! -- -- BUGS: 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 -- clamp - "clamp" used to avoid marks outside marks area, "clip" should be use instead but not working in Graph extension, see https://phabricator.wikimedia.org/T251709 -- TODO: -- marks: -- - line strokeDash + serialization, -- - symStroke serialization -- - symbolsNoFill serialization -- - arbitrary SVG path symbol shape as symbolsShape argument -- - annotations -- - vertical / horizontal line at specific values [DONE] 2020-09-01 -- - 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_): -- 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. localp={} --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 string debuglog="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) ifnotcsvthenreturnend locallist=mw.text.split(csv,"%s*,%s*") localresult={} localisInteger=true fori=1,#listdo iflist[i]==""then result[i]=nil else result[i]=tonumber(list[i]) ifnotresult[i]thenreturnend ifisIntegerthen localint,frac=math.modf(result[i]) isInteger=frac==0.0 end end end returnresult,isInteger end localfunctionstringArray(text) ifnottextthenreturnend locallist=mw.text.split(mw.ustring.gsub(tostring(text),"\\,","<COMMA>"),",",true) fori=1,#listdo list[i]=mw.ustring.gsub(mw.text.trim(list[i]),"<COMMA>",",") end returnlist end localfunctionisTable(t)returntype(t)=="table"end localfunctioncopy(x) iftype(x)=="table"then localresult={} forkey,valueinpairs(x)doresult[key]=copy(value)end returnresult else returnx end end functionp.map(frame) -- map path data for geographic objects localbasemap=frame.args.basemapor"Template:Graph:Map/Inner/Worldmap2c-json"-- WorldMap name and/or location may vary from wiki to wiki -- scaling factor localscale=tonumber(frame.args.scale)or100 -- map projection, see https://github.com/mbostock/d3/wiki/Geo-Projections localprojection=frame.args.projectionor"equirectangular" -- defaultValue for geographic objects without data localdefaultValue=frame.args.defaultValueorframe.args.defaultvalue localscaleType=frame.args.scaleTypeorframe.args.scaletypeor"linear" -- 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 legend locallegend=frame.args.legend -- the map feature to display localfeature=frame.args.featureor"countries" -- map center localcenter=numericArray(frame.args.center) -- format JSON output localformatJson=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 data localvalues={} localisNumbers=nil forname,valueinpairs(frame.args)do ifmw.ustring.find(name,"^[^%l]+$")andvalueandvalue~=""then ifisNumbers==nilthenisNumbers=tonumber(value)end localdata={id=name,v=value} ifisNumbersthendata.v=tonumber(data.v)end table.insert(values,data) end end ifnotdefaultValuethen ifisNumbersthendefaultValue=0elsedefaultValue="silver"end end -- create highlight scale localscales ifisNumbersthen ifcolorScalethencolorScale=string.lower(colorScale)end ifcolorScale=="category10"orcolorScale=="category20"thenelsecolorScale=stringArray(colorScale)end scales= { { name="color", type=scaleType, domain={data="highlights",field="v"}, range=colorScale, nice=true, zero=false } } ifdomainMinthenscales[1].domainMin=domainMinend ifdomainMaxthenscales[1].domainMax=domainMaxend localexponent=string.match(scaleType,"pow%s+(%d+%.?%d+)")-- check for exponent ifexponentthen scales[1].type="pow" scales[1].exponent=exponent end end -- create legend iflegendthen legend= { { fill="color", offset=120, properties= { title={fontSize={value=14}}, labels={fontSize={value=12}}, legend= { stroke={value="silver"}, strokeWidth={value=1.5} } } } } end -- get map url localbasemapUrl if(string.sub(basemap,1,10)=="wikiraw://")then basemapUrl=basemap else -- if not a (supported) url look for a colon as namespace separator. If none prepend default map directory name. ifnotstring.find(basemap,":")thenbasemap=baseMapDirectory..basemapend basemapUrl="wikiraw:///"..mw.uri.encode(mw.title.new(basemap).prefixedText,"PATH") end localoutput= { version=2, width=1,-- generic value as output size depends solely on map size and scaling factor height=1,-- ditto data= { { -- data source for the highlights name="highlights", values=values }, { -- data source for map paths data name=feature, url=basemapUrl, format={type="topojson",feature=feature}, transform= { { -- geographic transformation ("geopath") of map paths data type="geopath", value="data",-- data source scale=scale, translate={0,0}, center=center, projection=projection }, { -- join ("zip") of mutiple data source: here map paths data and highlights type="lookup", keys={"id"},-- key for map paths data on="highlights",-- name of highlight data source onKey="id",-- key for highlight data source as={"zipped"},-- name of resulting table default={v=defaultValue}-- default value for geographic objects that could not be joined } } } }, marks= { -- output markings (map paths and highlights) { type="path", from={data=feature}, properties= { enter={path={field="layout_path"}}, update={fill={field="zipped.v"}}, hover={fill={value="darkgrey"}} } } }, legends=legend } if(scales)then output.scales=scales output.marks[1].properties.update.fill.scale="color" end localflags ifformatJsonthenflags=mw.text.JSON_PRETTYend returnmw.text.jsonEncode(output,flags) end localfunctiondeserializeXData(serializedX,xType,xMin,xMax) localx ifnotxTypeorxType=="integer"orxType=="number"then localisInteger x,isInteger=numericArray(serializedX) ifxthen xMin=tonumber(xMin) xMax=tonumber(xMax) ifnotxTypethen ifisIntegerthenxType="integer"elsexType="number"end end else ifxTypethenerror("Numbers expected for parameter 'x'")end end end ifnotxthen x=stringArray(serializedX) ifnotxTypethenxType="string"end end returnx,xType,xMin,xMax end localfunctiondeserializeYData(serializedYs,yType,yMin,yMax) localy={} localareAllInteger=true foryNum,valueinpairs(serializedYs)do localyValues ifnotyTypeoryType=="integer"oryType=="number"then localisInteger yValues,isInteger=numericArray(value) ifyValuesthen areAllInteger=areAllIntegerandisInteger else ifyTypethen error("Numbers expected for parameter '"..name.."'") else returndeserializeYData(serializedYs,"string",yMin,yMax) end end end ifnotyValuesthenyValues=stringArray(value)end y[yNum]=yValues end ifnotyTypethen ifareAllIntegerthenyType="integer"elseyType="number"end end ifyType=="integer"oryType=="number"then yMin=tonumber(yMin) yMax=tonumber(yMax) end returny,yType,yMin,yMax end localfunctionconvertXYToManySeries(x,y,xType,yType,seriesTitles) localdata= { name="chart", format= { type="json", parse={x=xType,y=yType} }, values={} } fori=1,#ydo localyLen=table.maxn(y[i]) forj=1,#xdo ifj<=yLenandy[i][j]thentable.insert(data.values,{series=seriesTitles[i],x=x[j],y=y[i][j]})end end end returndata end localfunctionconvertXYToSingleSeries(x,y,xType,yType,yNames) localdata={name="chart",format={type="json",parse={x=xType}},values={}} forj=1,#ydodata.format.parse[yNames[j]]=yTypeend fori=1,#xdo localitem={x=x[i]} forj=1,#ydoitem[yNames[j]]=y[j][i]end table.insert(data.values,item) end returndata end localfunctiongetXScale(chartType,stacked,xMin,xMax,xType,xScaleType) ifchartType=="pie"thenreturnend localxscale= { name="x", range="width", zero=false,-- do not include zero value domain={data="chart",field="x"} } ifxScaleTypethenxscale.type=xScaleTypeelsexscale.type="linear"end ifxMinthenxscale.domainMin=xMinend ifxMaxthenxscale.domainMax=xMaxend ifxMinorxMaxthen xscale.clamp=true xscale.nice=false end ifchartType=="rect"then xscale.type="ordinal" ifnotstackedthenxscale.padding=0.2end-- pad each bar group else ifxType=="date"then xscale.type="time" elseifxType=="string"then xscale.type="ordinal" xscale.points=true end end ifxTypeandxType~="date"andxScaleType~="log"thenxscale.nice=trueend-- force round numbers for x scale, but "log" and "date" scale outputs a wrong "nice" scale returnxscale end localfunctiongetYScale(chartType,stacked,yMin,yMax,yType,yScaleType) ifchartType=="pie"thenreturnend localyscale= { name="y", --type = yScaleType or "linear", range="height", -- area charts have the lower boundary of their filling at y=0 (see marks.properties.enter.y2), therefore these need to start at zero zero=chartType~="line", nice=yScaleType~="log"-- force round numbers for y scale, but log scale outputs a wrong "nice" scale } ifyScaleTypethenyscale.type=yScaleTypeelseyscale.type="linear"end ifyMinthenyscale.domainMin=yMinend ifyMaxthenyscale.domainMax=yMaxend ifyMinoryMaxthenyscale.clamp=trueend ifyType=="date"thenyscale.type="time" elseifyType=="string"thenyscale.type="ordinal"end ifstackedthen yscale.domain={data="stats",field="sum_y"} else yscale.domain={data="chart",field="y"} end returnyscale end localfunctiongetColorScale(colors,chartType,xCount,yCount) ifnotcolorsthen if(chartType=="pie"andxCount>10)oryCount>10thencolors="category20"elsecolors="category10"end end localcolorScale= { name="color", type="ordinal", range=colors, domain={data="chart",field="series"} } ifchartType=="pie"thencolorScale.domain.field="x"end returncolorScale end localfunctiongetAlphaColorScale(colors,y) localalphaScale -- if there is at least one color in the format "#aarrggbb", create a transparency (alpha) scale ifisTable(colors)then localalphas={} localhasAlpha=false fori=1,#colorsdo locala,rgb=string.match(colors[i],"#(%x%x)(%x%x%x%x%x%x)") ifathen hasAlpha=true alphas[i]=tostring(tonumber(a,16)/255.0) colors[i]="#"..rgb else alphas[i]="1" end end fori=#colors+1,#ydoalphas[i]="1"end ifhasAlphathenalphaScale={name="transparency",type="ordinal",range=alphas}end end returnalphaScale end localfunctiongetLineScale(linewidths,chartType) locallineScale={} lineScale= { name="line", type="ordinal", range=linewidths, domain={data="chart",field="series"} } returnlineScale end localfunctiongetSymSizeScale(symSize) localSymSizeScale={} SymSizeScale= { name="symSize", type="ordinal", range=symSize, domain={data="chart",field="series"} } returnSymSizeScale end localfunctiongetSymShapeScale(symShape) localSymShapeScale={} SymShapeScale= { name="symShape", type="ordinal", range=symShape, domain={data="chart",field="series"} } returnSymShapeScale end localfunctiongetValueScale(fieldName,min,max,type) localvalueScale= { name=fieldName, type=typeor"linear", domain={data="chart",field=fieldName}, range={min,max} } returnvalueScale end localfunctionaddInteractionToChartVisualisation(plotMarks,colorField,dataField) -- initial setup ifnotplotMarks.properties.enterthenplotMarks.properties.enter={}end plotMarks.properties.enter[colorField]={scale="color",field=dataField} -- action when cursor is over plot mark: highlight ifnotplotMarks.properties.hoverthenplotMarks.properties.hover={}end plotMarks.properties.hover[colorField]={value="red"} -- action when cursor leaves plot mark: reset to initial setup ifnotplotMarks.properties.updatethenplotMarks.properties.update={}end plotMarks.properties.update[colorField]={scale="color",field=dataField} end localfunctiongetPieChartVisualisation(yCount,innerRadius,outerRadius,linewidth,radiusScale) localchartvis= { type="arc", from={data="chart",transform={{field="y",type="pie"}}}, properties= { enter={ innerRadius={value=innerRadius}, outerRadius={}, startAngle={field="layout_start"}, endAngle={field="layout_end"}, stroke={value="white"}, strokeWidth={value=linewidthor1} } } } ifradiusScalethen chartvis.properties.enter.outerRadius.scale=radiusScale.name chartvis.properties.enter.outerRadius.field=radiusScale.domain.field else chartvis.properties.enter.outerRadius.value=outerRadius end addInteractionToChartVisualisation(chartvis,"fill","x") returnchartvis end localfunctiongetChartVisualisation(chartType,stacked,colorField,yCount,innerRadius,outerRadius,linewidth,alphaScale,radiusScale,lineScale,interpolate) ifchartType=="pie"thenreturngetPieChartVisualisation(yCount,innerRadius,outerRadius,linewidth,radiusScale)end localchartvis= { type=chartType, properties= { -- chart creation event handler enter= { x={scale="x",field="x"}, y={scale="y",field="y"} } } } addInteractionToChartVisualisation(chartvis,colorField,"series") ifcolorField=="stroke"then chartvis.properties.enter.strokeWidth={value=linewidthor2.5} iftype(lineScale)=="table"then chartvis.properties.enter.strokeWidth.value=nil chartvis.properties.enter.strokeWidth= { scale="line", field="series" } end end ifinterpolatethenchartvis.properties.enter.interpolate={value=interpolate}end ifalphaScalethenchartvis.properties.update[colorField.."Opacity"]={scale="transparency"}end -- for bars and area charts set the lower bound of their areas ifchartType=="rect"orchartType=="area"then ifstackedthen -- for stacked charts this lower bound is the end of the last stacking element chartvis.properties.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.properties.enter.y2={scale="y",value=0} end end -- for bar charts ... ifchartType=="rect"then -- set 1 pixel width between the bars chartvis.properties.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 grouping ifnotstackedandyCount>1then chartvis.properties.enter.x.scale="series" chartvis.properties.enter.x.field="series" chartvis.properties.enter.width.scale="series" end end -- stacked charts have their own (stacked) y values ifstackedthenchartvis.properties.enter.y.field="layout_start"end -- if there are multiple series group these together ifyCount==1then chartvis.from={data="chart"} else -- if there are multiple series, connect colors to series chartvis.properties.update[colorField].field="series" ifalphaScalethenchartvis.properties.update[colorField.."Opacity"].field="series"end -- if there are multiple series, connect linewidths to series ifchartype=="line"then chartvis.properties.update["strokeWidth"].field="series" end -- apply a grouping (facetting) transformation chartvis= { type="group", marks={chartvis}, from= { data="chart", transform= { { type="facet", groupby={"series"} } } } } -- for stacked charts apply a stacking transformation ifstackedthen table.insert(chartvis.from.transform,1,{type="stack",groupby={"x"},sortby={"-_id"},field="y"}) else -- for bar charts the series are side-by-side grouped by x ifchartType=="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 group localgroupScale= { name="series", type="ordinal", range="width", domain={field="series"} } chartvis.from.transform[1].groupby="x" chartvis.scales={groupScale} chartvis.properties={enter={x={field="key",scale="x"},width={scale="x",band=true}}} end end end returnchartvis end localfunctiongetTextMarks(chartvis,chartType,outerRadius,scales,radiusScale,yType,showValues) localproperties ifchartType=="rect"then properties= { x={scale=chartvis.properties.enter.x.scale,field=chartvis.properties.enter.x.field}, y={scale=chartvis.properties.enter.y.scale,field=chartvis.properties.enter.y.field,offset=-(tonumber(showValues.offset)or-4)}, --dx = { scale = chartvis.properties.enter.x.scale, band = true, mult = 0.5 }, -- for horizontal text dy={scale=chartvis.properties.enter.x.scale,band=true,mult=0.5},-- for vertical text align={}, baseline={value="middle"}, fill={}, angle={value=-90}, fontSize={value=tonumber(showValues.fontsize)or11} } ifproperties.y.offset>=0then properties.align.value="right" properties.fill.value=showValues.fontcoloror"white" else properties.align.value="left" properties.fill.value=showValues.fontcolororpersistentGrey end elseifchartType=="pie"then properties= { x={group="width",mult=0.5}, y={group="height",mult=0.5}, radius={offset=tonumber(showValues.offset)or-4}, theta={field="layout_mid"}, fill={value=showValues.fontcolororpersistentGrey}, baseline={}, angle={}, fontSize={value=tonumber(showValues.fontsize)ormath.ceil(outerRadius/10)} } if(showValues.angleor"midangle")=="midangle"then properties.align={value="center"} properties.angle={field="layout_mid",mult=180.0/math.pi} ifproperties.radius.offset>=0then properties.baseline.value="bottom" else ifnotshowValues.fontcolorthenproperties.fill.value="white"end properties.baseline.value="top" end elseiftonumber(showValues.angle)then -- qunatize scale for aligning text left on right half-circle and right on left half-circle localalignScale={name="align",type="quantize",domainMin=0.0,domainMax=math.pi*2,range={"left","right"}} table.insert(scales,alignScale) properties.align={scale=alignScale.name,field="layout_mid"} properties.angle={value=tonumber(showValues.angle)} properties.baseline.value="middle" ifnottonumber(showValues.offset)thenproperties.radius.offset=4end end ifradiusScalethen properties.radius.scale=radiusScale.name properties.radius.field=radiusScale.domain.field else properties.radius.value=outerRadius end end ifpropertiesthen ifshowValues.formatthen localtemplate="datum.y" ifyType=="integer"oryType=="number"thentemplate=template.."|number:'"..showValues.format.."'" elseifyType=="date"thentemplate=template.."|time:"..showValues.format.."'" end properties.text={template="{{"..template.."}}"} else properties.text={field="y"} end localtextmarks= { type="text", properties= { enter=properties } } ifchartvis.fromthentextmarks.from=copy(chartvis.from)end returntextmarks end end localfunctiongetSymbolMarks(chartvis,symSize,symShape,symStroke,noFill,alphaScale) localsymbolmarks symbolmarks= { type="symbol", properties= { 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"then symbolmarks.properties.enter.shape={value=symShape} end iftype(symShape)=="table"then symbolmarks.properties.enter.shape={scale="symShape",field="series"} end iftype(symSize)=="number"then symbolmarks.properties.enter.size={value=symSize} end iftype(symSize)=="table"then symbolmarks.properties.enter.size={scale="symSize",field="series"} end ifnoFillthen symbolmarks.properties.enter.fill=nil end ifalphaScalethen symbolmarks.properties.enter.fillOpacity= {scale="transparency",field="series"} symbolmarks.properties.enter.strokeOpacity= {scale="transparency",field="series"} end ifchartvis.fromthensymbolmarks.from=copy(chartvis.from)end returnsymbolmarks end localfunctiongetAnnoMarks(chartvis,stroke,fill,opacity) localvannolines,hannolines,vannoLabels,vannoLabels vannolines= { type="rule", from={data="v_anno"}, properties= { 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"}, properties= { 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"}, properties= { 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"}, properties= { 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,hannolabels end localfunctiongetAxes(xTitle,xAxisFormat,xAxisAngle,xType,xGrid,yTitle,yAxisFormat,yType,yGrid,chartType) localxAxis,yAxis ifchartType~="pie"then ifxType=="integer"andnotxAxisFormatthenxAxisFormat="d"end xAxis= { type="x", scale="x", title=xTitle, format=xAxisFormat, grid=xGrid } ifxAxisAnglethen localxAxisAlign ifxAxisAngle<0thenxAxisAlign="right"elsexAxisAlign="left"end xAxis.properties= { 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} } } else xAxis.properties= { title= { fill={value=persistentGrey} }, labels= { fill={value=persistentGrey} }, ticks= { stroke={value=persistentGrey} }, axis= { stroke={value=persistentGrey}, strokeWidth={value=2} }, grid= { stroke={value=persistentGrey} } } end ifyType=="integer"andnotyAxisFormatthenyAxisFormat="d"end yAxis= { type="y", scale="y", title=yTitle, format=yAxisFormat, grid=yGrid } yAxis.properties= { title= { fill={value=persistentGrey} }, labels= { fill={value=persistentGrey} }, ticks= { stroke={value=persistentGrey} }, axis= { stroke={value=persistentGrey}, strokeWidth={value=2} }, grid= { stroke={value=persistentGrey} } } end returnxAxis,yAxis end localfunctiongetLegend(legendTitle,chartType,outerRadius) locallegend= { fill="color", stroke="color", title=legendTitle, } legend.properties={ title={ fill={value=persistentGrey}, }, labels={ fill={value=persistentGrey}, }, } ifchartType=="pie"then legend.properties={ -- move legend from center position to top legend={ y={value=-outerRadius}, }, title={ fill={value=persistentGrey} }, labels={ fill={value=persistentGrey}, }, } end returnlegend end functionp.chart(frame) -- chart width and height localgraphwidth=tonumber(frame.args.width)or200 localgraphheight=tonumber(frame.args.height)or200 -- chart type localchartType=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), monotone localinterpolate=frame.args.interpolate -- mark colors (if no colors are given, the default 10 color palette is used) localcolorString=frame.args.colors ifcolorStringthencolorString=string.lower(colorString)end localcolors=stringArray(colorString) -- for line charts, the thickness of the line; for pie charts the gap between each slice locallinewidth=tonumber(frame.args.linewidth) locallinewidthsString=frame.args.linewidths locallinewidths iflinewidthsStringandlinewidthsString~=""thenlinewidths=numericArray(linewidthsString)orfalseend -- x and y axis caption localxTitle=frame.args.xAxisTitleorframe.args.xaxistitle localyTitle=frame.args.yAxisTitleorframe.args.yaxistitle -- x and y value types localxType=frame.args.xTypeorframe.args.xtype localyType=frame.args.yTypeorframe.args.ytype -- override x and y axis minimum and maximum localxMin=frame.args.xAxisMinorframe.args.xaxismin localxMax=frame.args.xAxisMaxorframe.args.xaxismax localyMin=frame.args.yAxisMinorframe.args.yaxismin localyMax=frame.args.yAxisMaxorframe.args.yaxismax -- override x and y axis label formatting localxAxisFormat=frame.args.xAxisFormatorframe.args.xaxisformat localyAxisFormat=frame.args.yAxisFormatorframe.args.yaxisformat localxAxisAngle=tonumber(frame.args.xAxisAngle)ortonumber(frame.args.xaxisangle) -- x and y scale types localxScaleType=frame.args.xScaleTypeorframe.args.xscaletype localyScaleType=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 grid localxGrid=frame.args.xGridorframe.args.xgridorfalse localyGrid=frame.args.yGridorframe.args.ygridorfalse -- for line chart, show a symbol at each data point localshowSymbols=frame.args.showSymbolsorframe.args.showsymbols localsymbolsShape=frame.args.symbolsShapeorframe.args.symbolsshape localsymbolsNoFill=frame.args.symbolsNoFillorframe.args.symbolsnofill localsymbolsStroke=tonumber(frame.args.symbolsStrokeorframe.args.symbolsstroke) -- show legend with given title locallegendTitle=frame.args.legend -- show values as text localshowValues=frame.args.showValuesorframe.args.showvalues -- show v- and h-line annotations localv_annoLineString=frame.args.vAnnotatonsLineorframe.args.vannotatonsline localh_annoLineString=frame.args.hAnnotatonsLineorframe.args.hannotatonsline localv_annoLabelString=frame.args.vAnnotatonsLabelorframe.args.vannotatonslabel localh_annoLabelString=frame.args.hAnnotatonsLabelorframe.args.hannotatonslabel -- decode annotations cvs localv_annoLine,v_annoLabel,h_annoLine,h_annoLabel ifv_annoLineStringandv_annoLineString~=""then ifxType=="number"orxType=="integer"then v_annoLine=numericArray(v_annoLineString) else v_annoLine=stringArray(v_annoLineString) end v_annoLabel=stringArray(v_annoLabelString) end ifh_annoLineStringandh_annoLineString~=""then ifyType=="number"oryType=="integer"then h_annoLine=numericArray(h_annoLineString) else h_annoLine=stringArray(h_annoLineString) end h_annoLabel=stringArray(h_annoLabelString) end -- pie chart radiuses localinnerRadius=tonumber(frame.args.innerRadius)ortonumber(frame.args.innerradius)or0 localouterRadius=math.min(graphwidth,graphheight) -- format JSON output localformatJson=frame.args.formatjson -- get x values localx x,xType,xMin,xMax=deserializeXData(frame.args.x,xType,xMin,xMax) -- get y values (series) localyValues={} localseriesTitles={} forname,valueinpairs(frame.args)do localyNum ifname=="y"thenyNum=1elseyNum=tonumber(string.match(name,"^y(%d+)$"))end ifyNumthen yValues[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"]orname end end localy y,yType,yMin,yMax=deserializeYData(yValues,yType,yMin,yMax) -- create data tuples, consisting of series index, x value, y value localdata ifchartType=="pie"then -- for pie charts the second second series is merged into the first series as radius values data=convertXYToSingleSeries(x,y,xType,yType,{"y","r"}) else data=convertXYToManySeries(x,y,xType,yType,seriesTitles) end -- configure stacked charts localstacked=false localstats ifstring.sub(chartType,1,7)=="stacked"then chartType=string.sub(chartType,8) if#y>1then-- ignore stacked charts if there is only one series stacked=true -- aggregate data by cumulative y values stats= { name="stats",source="chart",transform= { { type="aggregate", groupby={"x"}, summarize={y="sum"} } } } end end -- add annotations to data localvannoData,hannoData ifv_annoLinethen vannoData={name="v_anno",format={type="json",parse={x=xType}},values={}} fori=1,#v_annoLinedo localitem={x=v_annoLine[i],label=v_annoLabel[i]} table.insert(vannoData.values,item) end end ifh_annoLinethen hannoData={name="h_anno",format={type="json",parse={y=yType}},values={}} fori=1,#h_annoLinedo localitem={y=h_annoLine[i],label=h_annoLabel[i]} table.insert(hannoData.values,item) end end -- create scales localscales={} 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) locallineScale if(linewidths)and(chartType=="line")then lineScale=getLineScale(linewidths,chartType) table.insert(scales,lineScale) end localradiusScale ifchartType=="pie"and#y>1then radiusScale=getValueScale("r",0,outerRadius) table.insert(scales,radiusScale) end -- decide if lines (strokes) or areas (fills) should be drawn localcolorField ifchartType=="line"thencolorField="stroke"elsecolorField="fill"end -- create chart markings localchartvis=getChartVisualisation(chartType,stacked,colorField,#y,innerRadius,outerRadius,linewidth,alphaScale,radiusScale,lineScale,interpolate) localmarks={chartvis} -- text marks ifshowValuesthen iftype(showValues)=="string"then-- deserialize as table localkeyValues=mw.text.split(showValues,"%s*,%s*") showValues={} for_,kvinipairs(keyValues)do localkey,value=mw.ustring.match(kv,"^%s*(.-)%s*:%s*(.-)%s*$") ifkeythenshowValues[key]=valueend end end localchartmarks=chartvis ifchartmarks.marksthenchartmarks=chartmarks.marks[1]end localtextmarks=getTextMarks(chartmarks,chartType,outerRadius,scales,radiusScale,yType,showValues) ifchartmarks~=chartvisthen table.insert(chartvis.marks,textmarks) else table.insert(marks,textmarks) end end -- grids ifxGridthen ifxGrid=="0"thenxGrid=false elseifxGrid==0thenxGrid=false elseifxGrid=="false"thenxGrid=false elseifxGrid=="n"thenxGrid=false elsexGrid=true end end ifyGridthen ifyGrid=="0"thenyGrid=false elseifyGrid==0thenyGrid=false elseifyGrid=="false"thenyGrid=false elseifyGrid=="n"thenyGrid=false elseyGrid=true end end -- symbol marks ifshowSymbolsandchartType~="rect"then localchartmarks=chartvis ifchartmarks.marksthenchartmarks=chartmarks.marks[1]end iftype(showSymbols)=="string"then ifshowSymbols==""thenshowSymbols=true elseshowSymbols=numericArray(showSymbols) end else showSymbols=tonumber(showSymbols) end -- custom size localsymSize iftype(showSymbols)=="number"then symSize=tonumber(showSymbols*showSymbols*8.5) elseiftype(showSymbols)=="table"then symSize={} fork,vinpairs(showSymbols)do symSize[k]=v*v*8.5-- "size" acc to Vega syntax is area of symbol end else symSize=50 end -- symSizeScale localsymSizeScale={} iftype(symSize)=="table"then symSizeScale=getSymSizeScale(symSize) table.insert(scales,symSizeScale) end -- custom shape ifstringArray(symbolsShape)and#stringArray(symbolsShape)>1thensymbolsShape=stringArray(symbolsShape)end localsymShape=" " iftype(symbolsShape)=="string"andshapes[symbolsShape]then symShape=shapes[symbolsShape] elseiftype(symbolsShape)=="table"then symShape={} fork,vinpairs(symbolsShape)do ifsymbolsShape[k]andshapes[symbolsShape[k]]then symShape[k]=shapes[symbolsShape[k]] else symShape[k]="circle" end end else symShape="circle" end -- symShapeScale localsymShapeScale={} iftype(symShape)=="table"then symShapeScale=getSymShapeScale(symShape) table.insert(scales,symShapeScale) end -- custom stroke localsymStroke if(type(symbolsStroke)=="number")then symStroke=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 -- end else symStroke=0 --always draw x with stroke ifsymbolsShape=="x"thensymStroke=2.5end --always draw x with stroke ifsymbolsNoFillthensymStroke=2.5end end -- TODO -- symStrokeScale -- local symStrokeScale = {} -- if type(symStroke) == "table" then -- symStrokeScale = getSymStrokeScale(symStroke) -- table.insert(scales, symStrokeScale) -- end localsymbolmarks=getSymbolMarks(chartmarks,symSize,symShape,symStroke,symbolsNoFill,alphaScale) ifchartmarks~=chartvisthen table.insert(chartvis.marks,symbolmarks) else table.insert(marks,symbolmarks) end end localvannolines,vannolabels,hannolines,hannolabels=getAnnoMarks(chartmarks,persistentGrey,persistentGrey,0.75) ifvannoDatathen table.insert(marks,vannolines) table.insert(marks,vannolabels) end ifhannoDatathen table.insert(marks,hannolines) table.insert(marks,hannolabels) end -- axes localxAxis,yAxis=getAxes(xTitle,xAxisFormat,xAxisAngle,xType,xGrid,yTitle,yAxisFormat,yType,yGrid,chartType) -- legend locallegend iflegendTitleandtonumber(legendTitle)~=0thenlegend=getLegend(legendTitle,chartType,outerRadius)end -- construct final output object localoutput= { version=2, width=graphwidth, height=graphheight, data={data}, scales=scales, axes={xAxis,yAxis}, marks=marks, legends={legend} } ifvannoDatathentable.insert(output.data,vannoData)end ifhannoDatathentable.insert(output.data,hannoData)end ifstatsthentable.insert(output.data,stats)end localflags ifformatJsonthenflags=mw.text.JSON_PRETTYend returnmw.text.jsonEncode(output,flags) end functionp.mapWrapper(frame) returnp.map(frame:getParent()) end functionp.chartWrapper(frame) returnp.chart(frame:getParent()) end functionp.chartDebuger(frame) return"\n\nchart JSON\n "..p.chart(frame).." \n\n"..debuglog end -- 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 graph functionp.encodeTitleForPath(frame) returnmw.uri.encode(mw.text.decode(mw.text.trim(frame.args[1])),'PATH') end functionp.chartCompatible(frame) localnot_chart_args={ "colors", "xAxisMin", "xAxisMax", "yAxisMin", "yAxisMax", "xAxisAngle", "xScaleType", "yScaleType", "linewidth", "linewidths", "showSymbols", "symbolsShape", "symbolsNoFill", "symbolsStroke", "showValues", "innerRadius", "xGrid", "yGrid", "annotations" } localpframe=frame:getParent() localb=0 localt=0 fort=1,18,1do ifpframe.args[not_chart_args[t]]then break else b=b+1 end t=t+1 end ifb==18then ifpframe.args["name"]then return"[[Kategori:Grafik untuk Porting]]" else return"[[Kategori:Grafik yang perlu diporting dan membutuhkan nama]]" end end end returnp