🌟 | 現在、 鉄壁ヘッドショットには対応済みです。 鉄壁HSは通常HSと同じダメージになります。LMG及びDMR、チャージライフル、ハンマーポイント弾を除き、すべてのダメージ値が一致していることを確認しています。 |
「モジュール:Utility/TableUtil」の版間の差分
ナビゲーションに移動
検索に移動
(ヘッダーのcolspanを指定できるように変更) |
細 ([LineCell] 参照するオブジェクトのミスを修正) |
||
(同じ利用者による、間の25版が非表示) | |||
1行目: | 1行目: | ||
-- Table Utility for MediaWiki | |||
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
-- Copyright (C) 2021 mntone. All rights reserved. | |||
-- This script is released under the MIT License. | |||
-- ==================== | -- ==================== | ||
-- namespace: TableUtil | -- namespace: TableUtil | ||
8行目: | 12行目: | ||
-- ===== | -- ===== | ||
local aw = require('Module:Utility/Library') | local aw = require('Module:Utility/Library') | ||
local scale = require('Module:Utility/Scale') | |||
local function __passthrough(num) | local function __passthrough(num) | ||
return num | return num | ||
27行目: | 32行目: | ||
} | } | ||
if opts then | if opts then | ||
obj.attributes | if type(opts.attributes) == 'table' then | ||
obj.cellClass | obj.attributes = opts.attributes | ||
obj.headerClass | end | ||
obj.headerColspan = opts.headerColspan | |||
obj.headerStyle | if type(opts.cellClass) == 'string' then | ||
obj.cellClass = opts.cellClass | |||
end | |||
if type(opts.footerClass) == 'string' then | |||
obj.footerClass = opts.footerClass | |||
end | |||
if type(opts.headerClass) == 'string' then | |||
obj.headerClass = opts.headerClass | |||
end | |||
if type(opts.headerColspan) == 'number' then | |||
obj.headerColspan = opts.headerColspan | |||
else | |||
obj.headerColspan = 1 | |||
end | |||
if type(opts.headerStyle) == 'table' then | |||
obj.headerStyle = opts.headerStyle | |||
end | |||
else | else | ||
obj.headerColspan = 1 | obj.headerColspan = 1 | ||
end | end | ||
53行目: | 77行目: | ||
local headerCell = headerRow:tag('th'):wikitext(self.name) | local headerCell = headerRow:tag('th'):wikitext(self.name) | ||
if | if self.headerClass then | ||
headerCell:addClass(self.headerClass) | headerCell:addClass(self.headerClass) | ||
end | end | ||
59行目: | 83行目: | ||
headerCell:attr('colspan', self.headerColspan) | headerCell:attr('colspan', self.headerColspan) | ||
end | end | ||
if | if self.headerStyle then | ||
headerCell:css(self.headerStyle) | headerCell:css(self.headerStyle) | ||
end | end | ||
return headerCell | return headerCell | ||
end | |||
-- [Function] Render footer | |||
function __TableUtil__Cell:renderFooter(footerRow) | |||
if self.headerColspan <= 0 then | |||
return nil | |||
end | |||
local footerCell = footerRow:tag('td') | |||
if self.footerClass then | |||
footerCell:addClass(self.footerClass) | |||
end | |||
if self.headerColspan > 1 then | |||
footerCell:attr('colspan', self.headerColspan) | |||
end | |||
return footerCell | |||
end | end | ||
-- [Function] Render | -- [Function] Render | ||
function __TableUtil__Cell:render(row) | function __TableUtil__Cell:render(row) | ||
local cell = row:tag('td'):attr(self.attributes) | local cell = row:tag('td') | ||
if | if self.attributes then | ||
cell:attr(self.attributes) | |||
end | |||
if self.cellClass then | |||
cell:addClass(self.cellClass) | cell:addClass(self.cellClass) | ||
end | end | ||
103行目: | 146行目: | ||
-- [Function] Render | -- [Function] Render | ||
function __TableUtil__ValueCell:render(row, dataset, i | function __TableUtil__ValueCell:render(row, dataset, i) | ||
local value = dataset[self.dataIndex] | local value = dataset[self.dataIndex] | ||
local formattedValue = self.format(value) | local formattedValue = self.format(value) | ||
local cell = self:__cell__render(row, dataset, i | local cell = self:__cell__render(row, dataset, i) | ||
:attr('data-sort-value', value) | :attr('data-sort-value', value) | ||
:wikitext(formattedValue) | :wikitext(formattedValue) | ||
139行目: | 182行目: | ||
-- [Function] Reset context | -- [Function] Reset context | ||
function __TableUtil__RankCell:resetContext() | function __TableUtil__RankCell:resetContext(datainfo) | ||
self.currentRank = 0 | self.currentRank = 0 | ||
self.currentValue = -1 | self.currentValue = -1 | ||
152行目: | 195行目: | ||
end | end | ||
local cell = row:tag('th') | local cell = row:tag('th') | ||
:attr('data-sort-value', | :attr('data-sort-value', self.currentRank) | ||
:wikitext(self.currentRank) | :wikitext(self.currentRank) | ||
return cell | return cell | ||
168行目: | 211行目: | ||
} | } | ||
setmetatable(__TableUtil__LineCell, { __index = __TableUtil__ValueCell }) | setmetatable(__TableUtil__LineCell, { __index = __TableUtil__ValueCell }) | ||
-- [Utils] | |||
local function multipleGradient(colorStop1, colorStop2) | |||
local gradientFormat = string.format('linear-gradient(to right,transparent %%s%%%%,%s %%s%%%%,%s %%s%%%%,transparent %%s%%%%)', colorStop1, colorStop2) | |||
return function(minValue, maxValue) | |||
if minValue == maxValue then | |||
local value = 0.001 * aw.round(1000 * minValue) | |||
minValue = value - 0.5 | |||
maxValue = value + 0.5 | |||
else | |||
minValue = 0.001 * aw.round(1000 * minValue) | |||
maxValue = 0.001 * aw.round(1000 * maxValue) | |||
end | |||
return string.format(gradientFormat, minValue, minValue, maxValue, maxValue) | |||
end | |||
end | |||
local function singleGradient(colorStop1, colorStop2) | |||
local gradientFormat | |||
if colorStop1 == colorStop2 then | |||
gradientFormat = string.format('linear-gradient(to right,%s %%s%%%%,transparent %%s%%%%)', colorStop1) | |||
else | |||
gradientFormat = string.format('linear-gradient(to right,%s,%s %%s%%%%,transparent %%s%%%%)', colorStop1, colorStop2) | |||
end | |||
return function(value) | |||
value = 0.001 * aw.round(1000 * value) | |||
return string.format(gradientFormat, value, value) | |||
end | |||
end | |||
-- [Constructor] | -- [Constructor] | ||
function __TableUtil__LineCell.__new(typename, name, dataIndex, opts) | function __TableUtil__LineCell.__new(typename, name, dataIndex, opts) | ||
local obj = __TableUtil__Cell.__new(typename, name, opts) | local obj = __TableUtil__Cell.__new(typename, name, opts) | ||
obj.__cell__render = obj.render | obj.__cell__render = obj.render | ||
obj.__cell__renderFooter = obj.renderFooter | |||
obj.dataIndex = dataIndex | obj.dataIndex = dataIndex | ||
if type(opts) == 'table' then | if type(opts) == 'table' then | ||
obj.colorStops | obj.itemName = opts.itemName or 'Item 1' | ||
obj.height | obj.startIndex = opts.startIndex or dataIndex | ||
obj. | obj.endIndex = opts.endIndex or dataIndex | ||
obj.maximumRate | obj.colorStops = opts.colorStops or '#A7D7F9' | ||
obj.hoverColor = opts.hoverColor or '#5FB6F4' | |||
obj._hasSubGraph = opts.subStartIndex and opts.subEndIndex | |||
if obj._hasSubGraph then | |||
obj.height = opts.height or 1.0 | |||
obj.subItemName = opts.subItemName or 'Item 2' | |||
obj.subStartIndex = opts.subStartIndex or opts.subDataIndex | |||
obj.subEndIndex = opts.subEndIndex or opts.subDataIndex | |||
obj.subColorStops = opts.subColorStops or '#F9C9A7' | |||
obj.subHoverColor = opts.subHoverColor or '#F49D5F' | |||
obj.subHeight = opts.subHeight or 0.4 | |||
else | |||
obj.height = opts.height or 1.2 | |||
end | |||
if opts.gridEnabled then | |||
obj.gridEnabled = opts.gridEnabled | |||
else | |||
obj.gridEnabled = true | |||
end | |||
obj.hoverEnabled = opts.hoverEnabled or false | |||
obj.hoverFormat = opts.hoverFormat or __passthrough | |||
obj.minimumRate = opts.minimumRate or 0.95 | |||
obj.maximumValue = opts.maximumValue or math.huge | |||
obj.maximumRate = opts.maximumRate or 1.05 | |||
obj.preferredGridCount = opts.preferredGridCount or 4 | |||
obj.scale = opts.scale or scale.LinearScale.new():setClamp(true) | |||
if obj.scale.typename ~= scale.LinearScale.__typename then | |||
obj.minimumValue = opts.minimumValue or -math.huge | |||
else | |||
obj.minimumValue = opts.minimumValue or 0 | |||
end | |||
obj.ticksFormat = opts.ticksFormat or __passthrough | |||
else | else | ||
obj.colorStops | obj.itemName = 'Item 1' | ||
obj.height | obj.startIndex = dataIndex | ||
obj.minimumValue = 0 | obj.endIndex = dataIndex | ||
obj.maximumRate | obj.colorStops = '#A7D7F9' | ||
obj.hoverColor = '#5FB6F4' | |||
obj._hasSubGraph = false | |||
obj.subColorStops = '#F9C9A7' | |||
obj.subHoverColor = '#F49D5F' | |||
obj.gridEnabled = true | |||
obj.height = 1.2 | |||
obj.hoverEnabled = false | |||
obj.hoverFormat = __passthrough | |||
obj.minimumRate = 0.95 | |||
obj.minimumValue = 0 | |||
obj.maximumValue = math.huge | |||
obj.maximumRate = 1.05 | |||
obj.preferredGridCount = 4 | |||
obj.scale = scale.LinearScale.new():setClamp(true) | |||
obj.ticksFormat = __passthrough | |||
end | end | ||
obj._gridCount = 2 | |||
return setmetatable(obj, { __index = __TableUtil__LineCell }) | return setmetatable(obj, { __index = __TableUtil__LineCell }) | ||
end | end | ||
197行目: | 318行目: | ||
end | end | ||
-- [Function] | -- [Function] Reset context | ||
function __TableUtil__LineCell: | function __TableUtil__LineCell:resetContext(datainfo) | ||
local startValue, stopValue | |||
if type(self.colorStops) == 'table' then | if self.minimumValue == -math.huge then | ||
if self. | local minValue = datainfo[self.startIndex].minValue | ||
if self._hasSubGraph then | |||
local subMinValue = datainfo[self.subStartIndex].minValue | |||
if minValue < subMinValue then | |||
minValue = subMinValue | |||
end | |||
end | |||
startValue = self.minimumRate * minValue | |||
else | |||
startValue = self.minimumValue | |||
end | |||
if self.maximumValue == math.huge then | |||
local maxValue = datainfo[self.endIndex].maxValue | |||
if self._hasSubGraph then | |||
local subMaxValue = datainfo[self.subEndIndex].maxValue | |||
if maxValue < subMaxValue then | |||
maxValue = subMaxValue | |||
end | |||
end | |||
stopValue = self.maximumRate * maxValue | |||
else | |||
stopValue = self.maximumValue | |||
end | |||
self.scale | |||
:setDomain({ startValue, stopValue }) | |||
:nice(self.preferredGridCount) | |||
if self.gridEnabled then | |||
self._ticks = self.scale:ticks(self.preferredGridCount) | |||
end | |||
if self.startIndex == self.endIndex then | |||
if type(self.colorStops) == 'table' then | |||
self._gradientFormat = singleGradient(self.colorStops[1], self.colorStops[2]) | |||
else | |||
self._gradientFormat = singleGradient(self.colorStops, self.colorStops) | |||
end | |||
else | |||
if type(self.colorStops) == 'table' then | |||
self._gradientFormat = multipleGradient(self.colorStops[1], self.colorStops[2]) | |||
else | |||
self._gradientFormat = multipleGradient(self.colorStops, self.colorStops) | |||
end | |||
end | |||
if self._hasSubGraph then | |||
if self.subStartIndex == self.subEndIndex then | |||
if type(self.subColorStops) == 'table' then | |||
self._subGradientFormat = singleGradient(self.subColorStops[1], self.subColorStops[2]) | |||
else | |||
self._subGradientFormat = singleGradient(self.subColorStops, self.subColorStops) | |||
end | end | ||
else | else | ||
if type(self.subColorStops) == 'table' then | |||
self._subGradientFormat = multipleGradient(self.subColorStops[1], self.subColorStops[2]) | |||
else | |||
self._subGradientFormat = multipleGradient(self.subColorStops, self.subColorStops) | |||
end | end | ||
end | |||
if self.gridEnabled then | |||
if self.scale.typename == scale.LogScale.__typename then | |||
local ticksCount = #self._ticks + 1 | |||
self._classes = string.format('st-grid st-grid-%d st-grid-log%d st-line st-line-h%d-h%d', ticksCount, ticksCount, 10 * self.height, 10 * self.subHeight) | |||
else | |||
self._classes = string.format('st-grid st-grid-%d st-line st-line-h%d-h%d', #self._ticks + 1, 10 * self.height, 10 * self.subHeight) | |||
end | |||
else | |||
self._classes = string.format('st-line st-line-h%d-h%d', 10 * self.height, 10 * self.subHeight) | |||
end | |||
elseif self.gridEnabled then | |||
if self.scale.typename == scale.LogScale.__typename then | |||
local ticksCount = #self._ticks + 1 | |||
self._classes = string.format('st-grid st-grid-%d st-grid-log%d st-line st-line-h%d', ticksCount, ticksCount, 10 * self.height) | |||
else | |||
self._classes = string.format('st-grid st-grid-%d st-line st-line-h%d', #self._ticks + 1, 10 * self.height) | |||
end | end | ||
else | else | ||
self._classes = string.format('st-line st-line-h%d', 10 * self.height) | |||
end | |||
end | |||
-- [Function] Render footer | |||
function __TableUtil__LineCell:renderFooter(footerRow) | |||
local footerCell = self:__cell__renderFooter(footerRow) | |||
if self.gridEnabled and footerCell then | |||
footerCell:addClass('st-content') | |||
if self.mixinValue then | if self.mixinValue then | ||
footerCell:addClass('st-mobile-content') | |||
end | |||
local list = footerCell:tag('ul') | |||
:addClass('st-axis') | |||
:addClass(string.format('st-axis-%d', #self._ticks + 1)) | |||
local firstTickLabel = self.ticksFormat(self.scale.domain[1]) | |||
list:tag('li'):wikitext(firstTickLabel) | |||
for _, tick in ipairs(self._ticks) do | |||
local tickLabel = self.ticksFormat(tick) | |||
list:tag('li'):wikitext(tickLabel) | |||
end | |||
local lastTickLabel = self.ticksFormat(self.scale.domain[2]) | |||
list:tag('li'):wikitext(lastTickLabel) | |||
if self._hasSubGraph then | |||
local legend = footerCell:tag('ul'):addClass('st-legend') | |||
local item = legend:tag('li') | |||
local mark = item:tag('span'):addClass('st-legend-mark'):wikitext('■') | |||
if type(self.colorStops) == 'table' then | |||
mark:css('background-image', string.format('linear-gradient(to right,%s,%s)!important', self.colorStops[1], self.colorStops[2])) | |||
else | |||
mark:css('background-color', self.colorStops) | |||
end | end | ||
item:wikitext(string.format(' %s', self.itemName)) | |||
local subItem = legend:tag('li') | |||
local subMark = subItem:tag('span'):addClass('st-legend-mark'):wikitext('■') | |||
if type(self.subColorStops) == 'table' then | |||
subMark:css('background-image', string.format('linear-gradient(to right,%s,%s)!important', self.subColorStops[1], self.subColorStops[2])) | |||
else | |||
subMark:css('background-color', self.subColorStops) | |||
end | end | ||
subItem:wikitext(string.format(' %s', self.subItemName)) | |||
end | end | ||
end | end | ||
return footerCell | |||
end | |||
-- [Function] Render | |||
function __TableUtil__LineCell:render(row, dataset, i) | |||
local value = dataset[self.dataIndex] | |||
local cell = self:__cell__render(row, dataset, i) | |||
:addClass(self._classes) | |||
:attr('data-sort-value', value) | |||
local | local gradient | ||
if self.startIndex ~= self.endIndex then | |||
local minimumValue = dataset[self.startIndex] | |||
local percentage | local maximumValue = dataset[self.endIndex] | ||
if | local minimumPercentage = self.scale:scale(minimumValue) | ||
percentage = | local maximumPercentage = self.scale:scale(maximumValue) | ||
gradient = self._gradientFormat(minimumPercentage, maximumPercentage) | |||
if self.hoverEnabled then | |||
cell:tag('span') | |||
:addClass('st-label') | |||
:addClass('st-label-item1') | |||
:addClass('st-label-lower') | |||
:css('color', self.hoverColor) | |||
:css('right', string.format('%s%%', aw.roundx(100 - minimumPercentage, 3))) | |||
:wikitext(self.hoverFormat(minimumValue)) | |||
cell:tag('span') | |||
:addClass('st-label') | |||
:addClass('st-label-item1') | |||
:addClass('st-label-upper') | |||
:css('color', self.hoverColor) | |||
:css('left', string.format('%s%%', aw.roundx(maximumPercentage, 3))) | |||
:wikitext(self.hoverFormat(maximumValue)) | |||
end | |||
else | |||
local percentage = self.scale:scale(value) | |||
gradient = self._gradientFormat(percentage) | |||
if self.hoverEnabled then | |||
cell:tag('span') | |||
:addClass('st-label') | |||
:addClass('st-label-item1') | |||
:addClass('st-label-upper') | |||
:css('color', self.hoverColor) | |||
:css('left', string.format('%s%%', aw.roundx(percentage, 3))) | |||
:wikitext(self.hoverFormat(value)) | |||
end | |||
end | |||
if self._hasSubGraph then | |||
local subGradient | |||
if self.subStartIndex ~= self.subEndIndex then | |||
local minimumValue2 = dataset[self.subStartIndex] | |||
local maximumValue2 = dataset[self.subEndIndex] | |||
local minimumPercentage = self.scale:scale(minimumValue2) | |||
local maximumPercentage = self.scale:scale(maximumValue2) | |||
subGradient = self._subGradientFormat(minimumPercentage, maximumPercentage) | |||
if self.hoverEnabled then | |||
cell:tag('span') | |||
:addClass('st-label') | |||
:addClass('st-label-item2') | |||
:addClass('st-label-lower') | |||
:css('color', self.subHoverColor) | |||
:css('right', string.format('%s%%', aw.roundx(100 - minimumPercentage, 3))) | |||
:wikitext(self.hoverFormat(minimumValue2)) | |||
cell:tag('span') | |||
:addClass('st-label') | |||
:addClass('st-label-item2') | |||
:addClass('st-label-upper') | |||
:css('color', self.subHoverColor) | |||
:css('left', string.format('%s%%', aw.roundx(maximumPercentage, 3))) | |||
:wikitext(self.hoverFormat(maximumValue2)) | |||
end | |||
else | |||
local value2 = dataset[self.subDataIndex] | |||
local percentage = self.scale:scale(value) | |||
subGradient = self._subGradientFormat(percentage) | |||
if self.hoverEnabled then | |||
cell:tag('span') | |||
:addClass('st-label') | |||
:addClass('st-label-item2') | |||
:addClass('st-label-upper') | |||
:css('color', self.subHoverColor) | |||
:css('left', string.format('%s%%', aw.roundx(percentage, 3))) | |||
:wikitext(self.hoverFormat(value2)) | |||
end | |||
end | |||
if not self.mixinValue then | |||
cell:css('background-image', string.format('%s,%s!important', gradient, subGradient)) | |||
else | |||
cell:css('background-image', string.format('%s,%s', gradient, subGradient)) | |||
end | |||
else | else | ||
if not self.mixinValue then | |||
cell:css('background-image', string.format('%s!important', gradient)) | |||
else | |||
cell:css('background-image', gradient) | |||
end | |||
end | end | ||
return cell | return cell | ||
end | end | ||
274行目: | 562行目: | ||
function valueKlass.new(name, dataIndex, opts) | function valueKlass.new(name, dataIndex, opts) | ||
local obj = superklass.__new(valuetype, name, dataIndex, opts) | local obj = superklass.__new(valuetype, name, dataIndex, opts) | ||
obj.__super__render = obj.render | obj.__super__render = obj.render | ||
obj.__super__renderHeader = obj.renderHeader | |||
obj.__super__renderFooter = obj.renderFooter | |||
obj.mixinValue = true | obj.mixinValue = true | ||
if type(opts) == 'table' then | if type(opts) == 'table' then | ||
283行目: | 573行目: | ||
return setmetatable(obj, { __index = valueKlass }) | return setmetatable(obj, { __index = valueKlass }) | ||
end | end | ||
function valueKlass:render(row, dataset, i | function valueKlass:render(row, dataset, i) | ||
local value = dataset[self.dataIndex] | local value = dataset[self.dataIndex] | ||
local formattedValue = self.format(value) | local formattedValue = self.format(value) | ||
local cell = self:__super__render(row, dataset, i | local cell = self:__super__render(row, dataset, i) | ||
return cell:wikitext(formattedValue) | return cell:wikitext(formattedValue) | ||
end | end | ||
323行目: | 613行目: | ||
-- func: createTable | -- func: createTable | ||
-- ================= | -- ================= | ||
function tableutil.createTable(dataset, metadata) | function tableutil.createTable(dataset, metadata, opts) | ||
local tbl = mw.html.create('table') | local tbl = mw.html.create('table') | ||
:addClass('numbertable') | :addClass('numbertable') | ||
:addClass('sortable') | :addClass('sortable') | ||
:addClass(' | :addClass('st') | ||
:addClass( | if opts and type(opts.tableClass) == 'string' then | ||
tbl:addClass(opts.tableClass) | |||
end | |||
-- Build header | -- Build header | ||
335行目: | 627行目: | ||
local isDesc = false | local isDesc = false | ||
for _, m in ipairs(metadata) do | for _, m in ipairs(metadata) do | ||
if m.typename == tableutil.RankCell.__typename then | if m.typename == tableutil.RankCell.__typename then | ||
sortIndex = m.dataIndex | sortIndex = m.dataIndex | ||
382行目: | 671行目: | ||
end | end | ||
end | end | ||
end | |||
-- Reset context | |||
for _, m in ipairs(metadata) do | |||
-- Reset context for building process | |||
m:resetContext(datainfo) | |||
end | end | ||
388行目: | 683行目: | ||
local row = tbl:tag('tr') | local row = tbl:tag('tr') | ||
for k, m in ipairs(metadata) do | for k, m in ipairs(metadata) do | ||
m:render(row, d, i | m:render(row, d, i) | ||
end | end | ||
end | |||
-- Build footer | |||
local footerRow = tbl:tag('tr'):addClass('sortbottom') | |||
for _, m in ipairs(metadata) do | |||
-- Render footer | |||
m:renderFooter(footerRow) | |||
end | end | ||
402行目: | 704行目: | ||
tableutil.RankCell.new('', 1), | tableutil.RankCell.new('', 1), | ||
tableutil.ValueCell.new('Value', 1), | tableutil.ValueCell.new('Value', 1), | ||
tableutil.LineCell.new('Line', 1), | tableutil.LineCell.new('Line', 1, { cellClass = 'st-graph' }), | ||
tableutil.LineCell.new('LineGradient', 1, { colorStops = { '#F8B3BC', '#F26D7D' }, height = 1.4 }), | tableutil.LineCell.new('LineGradient', 1, { cellClass = 'st-graph', colorStops = { '#F8B3BC', '#F26D7D' }, height = 1.4 }), | ||
tableutil.LineValueCell.new('LineValue', 2, { cellClass = ' | tableutil.LineValueCell.new('LineValue', 2, { cellClass = 'st-graph st-textshadow' }), | ||
tableutil.LineCell.new('Line (Range)', 1, { cellClass = 'st-graph', startIndex = 1, endIndex = 2, scale = scale.LogScale.new() }), | |||
tableutil.LineCell.new('Line (Range & Sub)', 1, { cellClass = 'st-graph', startIndex = 1, endIndex = 2, subStartIndex = 1, subEndIndex = 3, hoverEnabled = true }), | |||
} | } | ||
local node = tableutil.createTable({{ 45, 110 }, { 40, 60 }, { 40, 120 }, { 50, 90 }}, metadata) | local node = tableutil.createTable({{ 45, 110, 140 }, { 40, 60, 90 }, { 40, 120, 190 }, { 50, 90, 110 }}, metadata) | ||
return tostring(node) | return tostring(node) | ||
end | end | ||
return tableutil | return tableutil |
2021年9月2日 (木) 11:35時点における最新版
このモジュールについての説明文ページを モジュール:Utility/TableUtil/doc に作成できます
-- Table Utility for MediaWiki -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Copyright (C) 2021 mntone. All rights reserved. -- This script is released under the MIT License. -- ==================== -- namespace: TableUtil -- ==================== local tableutil = {} -- ===== -- Config -- ===== local aw = require('Module:Utility/Library') local scale = require('Module:Utility/Scale') local function __passthrough(num) return num end -- ========== -- type: Cell -- ========== local __TableUtil__CellType__Cell = 'cell' local __TableUtil__Cell = { __typename = __TableUtil__CellType__Cell, } -- [Constructor] function __TableUtil__Cell.__new(typename, name, opts) local obj = { typename = typename, name = name, } if opts then if type(opts.attributes) == 'table' then obj.attributes = opts.attributes end if type(opts.cellClass) == 'string' then obj.cellClass = opts.cellClass end if type(opts.footerClass) == 'string' then obj.footerClass = opts.footerClass end if type(opts.headerClass) == 'string' then obj.headerClass = opts.headerClass end if type(opts.headerColspan) == 'number' then obj.headerColspan = opts.headerColspan else obj.headerColspan = 1 end if type(opts.headerStyle) == 'table' then obj.headerStyle = opts.headerStyle end else obj.headerColspan = 1 end return setmetatable(obj, { __index = __TableUtil__Cell }) end function __TableUtil__Cell.new(name, opts) return __TableUtil__Cell.__new(__TableUtil__CellType__Cell, name, opts) end -- [Function] Reset context function __TableUtil__Cell:resetContext() end -- [Function] Render header function __TableUtil__Cell:renderHeader(headerRow) if self.headerColspan <= 0 then return nil end local headerCell = headerRow:tag('th'):wikitext(self.name) if self.headerClass then headerCell:addClass(self.headerClass) end if self.headerColspan > 1 then headerCell:attr('colspan', self.headerColspan) end if self.headerStyle then headerCell:css(self.headerStyle) end return headerCell end -- [Function] Render footer function __TableUtil__Cell:renderFooter(footerRow) if self.headerColspan <= 0 then return nil end local footerCell = footerRow:tag('td') if self.footerClass then footerCell:addClass(self.footerClass) end if self.headerColspan > 1 then footerCell:attr('colspan', self.headerColspan) end return footerCell end -- [Function] Render function __TableUtil__Cell:render(row) local cell = row:tag('td') if self.attributes then cell:attr(self.attributes) end if self.cellClass then cell:addClass(self.cellClass) end return cell end -- [Export] tableutil.Cell = __TableUtil__Cell -- ===================== -- type: ValueCell: Cell -- ===================== local __TableUtil__CellType__ValueCell = 'value' local __TableUtil__ValueCell = { __typename = __TableUtil__CellType__ValueCell, } setmetatable(__TableUtil__ValueCell, { __index = __TableUtil__Cell }) -- [Constructor] function __TableUtil__ValueCell.__new(typename, name, dataIndex, opts) local obj = __TableUtil__Cell.__new(typename, name, opts) obj.__cell__render = obj.render obj.dataIndex = dataIndex if type(opts) == 'table' then obj.format = opts.format or __passthrough else obj.format = __passthrough end return setmetatable(obj, { __index = __TableUtil__ValueCell }) end function __TableUtil__ValueCell.new(name, dataIndex, opts) return __TableUtil__ValueCell.__new(__TableUtil__CellType__ValueCell, name, dataIndex, opts) end -- [Function] Render function __TableUtil__ValueCell:render(row, dataset, i) local value = dataset[self.dataIndex] local formattedValue = self.format(value) local cell = self:__cell__render(row, dataset, i) :attr('data-sort-value', value) :wikitext(formattedValue) return cell end -- [Export] tableutil.ValueCell = __TableUtil__ValueCell -- ========================= -- type: RankCell: ValueCell -- ========================= local __TableUtil__CellType__RankCell = 'rank' local __TableUtil__RankCell = { __typename = __TableUtil__CellType__RankCell, } setmetatable(__TableUtil__RankCell, { __index = __TableUtil__ValueCell }) -- [Constructor] function __TableUtil__RankCell.new(name, dataIndex, descending) local obj = __TableUtil__ValueCell.__new(__TableUtil__CellType__RankCell, name, dataIndex) obj.currentRank = 0 obj.currentValue = -1 obj.descending = descending return setmetatable(obj, { __index = __TableUtil__RankCell }) end -- [Property] Is descending? function __TableUtil__RankCell:isDescending() return self.descending end -- [Function] Reset context function __TableUtil__RankCell:resetContext(datainfo) self.currentRank = 0 self.currentValue = -1 end -- [Function] Render function __TableUtil__RankCell:render(row, dataset, i) local value = dataset[self.dataIndex] if self.currentValue ~= value then self.currentRank = i self.currentValue = value end local cell = row:tag('th') :attr('data-sort-value', self.currentRank) :wikitext(self.currentRank) return cell end -- [Export] tableutil.RankCell = __TableUtil__RankCell -- ==================== -- type: LineCell: Cell -- ==================== local __TableUtil__CellType__LineCell = 'line' local __TableUtil__LineCell = { __typename = __TableUtil__CellType__LineCell, } setmetatable(__TableUtil__LineCell, { __index = __TableUtil__ValueCell }) -- [Utils] local function multipleGradient(colorStop1, colorStop2) local gradientFormat = string.format('linear-gradient(to right,transparent %%s%%%%,%s %%s%%%%,%s %%s%%%%,transparent %%s%%%%)', colorStop1, colorStop2) return function(minValue, maxValue) if minValue == maxValue then local value = 0.001 * aw.round(1000 * minValue) minValue = value - 0.5 maxValue = value + 0.5 else minValue = 0.001 * aw.round(1000 * minValue) maxValue = 0.001 * aw.round(1000 * maxValue) end return string.format(gradientFormat, minValue, minValue, maxValue, maxValue) end end local function singleGradient(colorStop1, colorStop2) local gradientFormat if colorStop1 == colorStop2 then gradientFormat = string.format('linear-gradient(to right,%s %%s%%%%,transparent %%s%%%%)', colorStop1) else gradientFormat = string.format('linear-gradient(to right,%s,%s %%s%%%%,transparent %%s%%%%)', colorStop1, colorStop2) end return function(value) value = 0.001 * aw.round(1000 * value) return string.format(gradientFormat, value, value) end end -- [Constructor] function __TableUtil__LineCell.__new(typename, name, dataIndex, opts) local obj = __TableUtil__Cell.__new(typename, name, opts) obj.__cell__render = obj.render obj.__cell__renderFooter = obj.renderFooter obj.dataIndex = dataIndex if type(opts) == 'table' then obj.itemName = opts.itemName or 'Item 1' obj.startIndex = opts.startIndex or dataIndex obj.endIndex = opts.endIndex or dataIndex obj.colorStops = opts.colorStops or '#A7D7F9' obj.hoverColor = opts.hoverColor or '#5FB6F4' obj._hasSubGraph = opts.subStartIndex and opts.subEndIndex if obj._hasSubGraph then obj.height = opts.height or 1.0 obj.subItemName = opts.subItemName or 'Item 2' obj.subStartIndex = opts.subStartIndex or opts.subDataIndex obj.subEndIndex = opts.subEndIndex or opts.subDataIndex obj.subColorStops = opts.subColorStops or '#F9C9A7' obj.subHoverColor = opts.subHoverColor or '#F49D5F' obj.subHeight = opts.subHeight or 0.4 else obj.height = opts.height or 1.2 end if opts.gridEnabled then obj.gridEnabled = opts.gridEnabled else obj.gridEnabled = true end obj.hoverEnabled = opts.hoverEnabled or false obj.hoverFormat = opts.hoverFormat or __passthrough obj.minimumRate = opts.minimumRate or 0.95 obj.maximumValue = opts.maximumValue or math.huge obj.maximumRate = opts.maximumRate or 1.05 obj.preferredGridCount = opts.preferredGridCount or 4 obj.scale = opts.scale or scale.LinearScale.new():setClamp(true) if obj.scale.typename ~= scale.LinearScale.__typename then obj.minimumValue = opts.minimumValue or -math.huge else obj.minimumValue = opts.minimumValue or 0 end obj.ticksFormat = opts.ticksFormat or __passthrough else obj.itemName = 'Item 1' obj.startIndex = dataIndex obj.endIndex = dataIndex obj.colorStops = '#A7D7F9' obj.hoverColor = '#5FB6F4' obj._hasSubGraph = false obj.subColorStops = '#F9C9A7' obj.subHoverColor = '#F49D5F' obj.gridEnabled = true obj.height = 1.2 obj.hoverEnabled = false obj.hoverFormat = __passthrough obj.minimumRate = 0.95 obj.minimumValue = 0 obj.maximumValue = math.huge obj.maximumRate = 1.05 obj.preferredGridCount = 4 obj.scale = scale.LinearScale.new():setClamp(true) obj.ticksFormat = __passthrough end obj._gridCount = 2 return setmetatable(obj, { __index = __TableUtil__LineCell }) end function __TableUtil__LineCell.new(name, dataIndex, opts) return __TableUtil__LineCell.__new(__TableUtil__CellType__LineCell, name, dataIndex, opts) end -- [Property] Height function __TableUtil__LineCell:setHeight(value) self.height = value return self end -- [Function] Reset context function __TableUtil__LineCell:resetContext(datainfo) local startValue, stopValue if self.minimumValue == -math.huge then local minValue = datainfo[self.startIndex].minValue if self._hasSubGraph then local subMinValue = datainfo[self.subStartIndex].minValue if minValue < subMinValue then minValue = subMinValue end end startValue = self.minimumRate * minValue else startValue = self.minimumValue end if self.maximumValue == math.huge then local maxValue = datainfo[self.endIndex].maxValue if self._hasSubGraph then local subMaxValue = datainfo[self.subEndIndex].maxValue if maxValue < subMaxValue then maxValue = subMaxValue end end stopValue = self.maximumRate * maxValue else stopValue = self.maximumValue end self.scale :setDomain({ startValue, stopValue }) :nice(self.preferredGridCount) if self.gridEnabled then self._ticks = self.scale:ticks(self.preferredGridCount) end if self.startIndex == self.endIndex then if type(self.colorStops) == 'table' then self._gradientFormat = singleGradient(self.colorStops[1], self.colorStops[2]) else self._gradientFormat = singleGradient(self.colorStops, self.colorStops) end else if type(self.colorStops) == 'table' then self._gradientFormat = multipleGradient(self.colorStops[1], self.colorStops[2]) else self._gradientFormat = multipleGradient(self.colorStops, self.colorStops) end end if self._hasSubGraph then if self.subStartIndex == self.subEndIndex then if type(self.subColorStops) == 'table' then self._subGradientFormat = singleGradient(self.subColorStops[1], self.subColorStops[2]) else self._subGradientFormat = singleGradient(self.subColorStops, self.subColorStops) end else if type(self.subColorStops) == 'table' then self._subGradientFormat = multipleGradient(self.subColorStops[1], self.subColorStops[2]) else self._subGradientFormat = multipleGradient(self.subColorStops, self.subColorStops) end end if self.gridEnabled then if self.scale.typename == scale.LogScale.__typename then local ticksCount = #self._ticks + 1 self._classes = string.format('st-grid st-grid-%d st-grid-log%d st-line st-line-h%d-h%d', ticksCount, ticksCount, 10 * self.height, 10 * self.subHeight) else self._classes = string.format('st-grid st-grid-%d st-line st-line-h%d-h%d', #self._ticks + 1, 10 * self.height, 10 * self.subHeight) end else self._classes = string.format('st-line st-line-h%d-h%d', 10 * self.height, 10 * self.subHeight) end elseif self.gridEnabled then if self.scale.typename == scale.LogScale.__typename then local ticksCount = #self._ticks + 1 self._classes = string.format('st-grid st-grid-%d st-grid-log%d st-line st-line-h%d', ticksCount, ticksCount, 10 * self.height) else self._classes = string.format('st-grid st-grid-%d st-line st-line-h%d', #self._ticks + 1, 10 * self.height) end else self._classes = string.format('st-line st-line-h%d', 10 * self.height) end end -- [Function] Render footer function __TableUtil__LineCell:renderFooter(footerRow) local footerCell = self:__cell__renderFooter(footerRow) if self.gridEnabled and footerCell then footerCell:addClass('st-content') if self.mixinValue then footerCell:addClass('st-mobile-content') end local list = footerCell:tag('ul') :addClass('st-axis') :addClass(string.format('st-axis-%d', #self._ticks + 1)) local firstTickLabel = self.ticksFormat(self.scale.domain[1]) list:tag('li'):wikitext(firstTickLabel) for _, tick in ipairs(self._ticks) do local tickLabel = self.ticksFormat(tick) list:tag('li'):wikitext(tickLabel) end local lastTickLabel = self.ticksFormat(self.scale.domain[2]) list:tag('li'):wikitext(lastTickLabel) if self._hasSubGraph then local legend = footerCell:tag('ul'):addClass('st-legend') local item = legend:tag('li') local mark = item:tag('span'):addClass('st-legend-mark'):wikitext('■') if type(self.colorStops) == 'table' then mark:css('background-image', string.format('linear-gradient(to right,%s,%s)!important', self.colorStops[1], self.colorStops[2])) else mark:css('background-color', self.colorStops) end item:wikitext(string.format(' %s', self.itemName)) local subItem = legend:tag('li') local subMark = subItem:tag('span'):addClass('st-legend-mark'):wikitext('■') if type(self.subColorStops) == 'table' then subMark:css('background-image', string.format('linear-gradient(to right,%s,%s)!important', self.subColorStops[1], self.subColorStops[2])) else subMark:css('background-color', self.subColorStops) end subItem:wikitext(string.format(' %s', self.subItemName)) end end return footerCell end -- [Function] Render function __TableUtil__LineCell:render(row, dataset, i) local value = dataset[self.dataIndex] local cell = self:__cell__render(row, dataset, i) :addClass(self._classes) :attr('data-sort-value', value) local gradient if self.startIndex ~= self.endIndex then local minimumValue = dataset[self.startIndex] local maximumValue = dataset[self.endIndex] local minimumPercentage = self.scale:scale(minimumValue) local maximumPercentage = self.scale:scale(maximumValue) gradient = self._gradientFormat(minimumPercentage, maximumPercentage) if self.hoverEnabled then cell:tag('span') :addClass('st-label') :addClass('st-label-item1') :addClass('st-label-lower') :css('color', self.hoverColor) :css('right', string.format('%s%%', aw.roundx(100 - minimumPercentage, 3))) :wikitext(self.hoverFormat(minimumValue)) cell:tag('span') :addClass('st-label') :addClass('st-label-item1') :addClass('st-label-upper') :css('color', self.hoverColor) :css('left', string.format('%s%%', aw.roundx(maximumPercentage, 3))) :wikitext(self.hoverFormat(maximumValue)) end else local percentage = self.scale:scale(value) gradient = self._gradientFormat(percentage) if self.hoverEnabled then cell:tag('span') :addClass('st-label') :addClass('st-label-item1') :addClass('st-label-upper') :css('color', self.hoverColor) :css('left', string.format('%s%%', aw.roundx(percentage, 3))) :wikitext(self.hoverFormat(value)) end end if self._hasSubGraph then local subGradient if self.subStartIndex ~= self.subEndIndex then local minimumValue2 = dataset[self.subStartIndex] local maximumValue2 = dataset[self.subEndIndex] local minimumPercentage = self.scale:scale(minimumValue2) local maximumPercentage = self.scale:scale(maximumValue2) subGradient = self._subGradientFormat(minimumPercentage, maximumPercentage) if self.hoverEnabled then cell:tag('span') :addClass('st-label') :addClass('st-label-item2') :addClass('st-label-lower') :css('color', self.subHoverColor) :css('right', string.format('%s%%', aw.roundx(100 - minimumPercentage, 3))) :wikitext(self.hoverFormat(minimumValue2)) cell:tag('span') :addClass('st-label') :addClass('st-label-item2') :addClass('st-label-upper') :css('color', self.subHoverColor) :css('left', string.format('%s%%', aw.roundx(maximumPercentage, 3))) :wikitext(self.hoverFormat(maximumValue2)) end else local value2 = dataset[self.subDataIndex] local percentage = self.scale:scale(value) subGradient = self._subGradientFormat(percentage) if self.hoverEnabled then cell:tag('span') :addClass('st-label') :addClass('st-label-item2') :addClass('st-label-upper') :css('color', self.subHoverColor) :css('left', string.format('%s%%', aw.roundx(percentage, 3))) :wikitext(self.hoverFormat(value2)) end end if not self.mixinValue then cell:css('background-image', string.format('%s,%s!important', gradient, subGradient)) else cell:css('background-image', string.format('%s,%s', gradient, subGradient)) end else if not self.mixinValue then cell:css('background-image', string.format('%s!important', gradient)) else cell:css('background-image', gradient) end end return cell end -- [Export] tableutil.LineCell = __TableUtil__LineCell -- ================= -- type: Value+XCell -- ================= local function transformValueCell(superklass, klasstype) local valuetype = string.format('value+%s', klasstype) local valueKlass = { __typename = valuetype, } setmetatable(valueKlass, { __index = superklass }) function valueKlass.new(name, dataIndex, opts) local obj = superklass.__new(valuetype, name, dataIndex, opts) obj.__super__render = obj.render obj.__super__renderHeader = obj.renderHeader obj.__super__renderFooter = obj.renderFooter obj.mixinValue = true if type(opts) == 'table' then obj.format = opts.format or __passthrough else obj.format = __passthrough end return setmetatable(obj, { __index = valueKlass }) end function valueKlass:render(row, dataset, i) local value = dataset[self.dataIndex] local formattedValue = self.format(value) local cell = self:__super__render(row, dataset, i) return cell:wikitext(formattedValue) end return valueKlass end -- [Export] tableutil.LineValueCell = transformValueCell(__TableUtil__LineCell, __TableUtil__CellType__LineCell) -- =================== -- func: specifyUICell -- =================== function tableutil.specifyUICell(klasstype, renderFunction, propNames) local specifictype = string.format('ui+%s', klasstype) local specificKlass = { __typename = specifictype, } setmetatable(specificKlass, { __index = __TableUtil__Cell }) function specificKlass.new(name, dataIndex, opts) local obj = __TableUtil__Cell.__new(specifictype, name, opts) obj.__cell__render = obj.render obj.dataIndex = dataIndex if propNames and opts then for _, name in ipairs(propNames) do obj[name] = opts[name] end end return setmetatable(obj, { __index = specificKlass }) end specificKlass.render = renderFunction return specificKlass end -- ================= -- func: createTable -- ================= function tableutil.createTable(dataset, metadata, opts) local tbl = mw.html.create('table') :addClass('numbertable') :addClass('sortable') :addClass('st') if opts and type(opts.tableClass) == 'string' then tbl:addClass(opts.tableClass) end -- Build header local headerRow = tbl:tag('tr'):addClass('row-sticky') local sortIndex = 0 local isDesc = false for _, m in ipairs(metadata) do if m.typename == tableutil.RankCell.__typename then sortIndex = m.dataIndex isDesc = m:isDescending() end -- Render header m:renderHeader(headerRow) end -- Sort data if sortIndex ~= 0 then local sortFunction if isDesc then sortFunction = function(a, b) return a[sortIndex] > b[sortIndex] end else sortFunction = function(a, b) return a[sortIndex] < b[sortIndex] end end table.sort(dataset, sortFunction) end -- Preprocess data local datainfo = {} for _, ary in ipairs(dataset) do for key, d in pairs(ary) do if type(d) == 'number' and d ~= math.huge then if not datainfo[key] then datainfo[key] = { minValue = d, maxValue = d, } else if datainfo[key].minValue > d then datainfo[key].minValue = d end if datainfo[key].maxValue < d then datainfo[key].maxValue = d end end end end end -- Reset context for _, m in ipairs(metadata) do -- Reset context for building process m:resetContext(datainfo) end -- Build cell for i, d in ipairs(dataset) do local row = tbl:tag('tr') for k, m in ipairs(metadata) do m:render(row, d, i) end end -- Build footer local footerRow = tbl:tag('tr'):addClass('sortbottom') for _, m in ipairs(metadata) do -- Render footer m:renderFooter(footerRow) end return tbl end -- ================= -- test: Simple test -- ================= function tableutil.__test() local metadata = { tableutil.RankCell.new('', 1), tableutil.ValueCell.new('Value', 1), tableutil.LineCell.new('Line', 1, { cellClass = 'st-graph' }), tableutil.LineCell.new('LineGradient', 1, { cellClass = 'st-graph', colorStops = { '#F8B3BC', '#F26D7D' }, height = 1.4 }), tableutil.LineValueCell.new('LineValue', 2, { cellClass = 'st-graph st-textshadow' }), tableutil.LineCell.new('Line (Range)', 1, { cellClass = 'st-graph', startIndex = 1, endIndex = 2, scale = scale.LogScale.new() }), tableutil.LineCell.new('Line (Range & Sub)', 1, { cellClass = 'st-graph', startIndex = 1, endIndex = 2, subStartIndex = 1, subEndIndex = 3, hoverEnabled = true }), } local node = tableutil.createTable({{ 45, 110, 140 }, { 40, 60, 90 }, { 40, 120, 190 }, { 50, 90, 110 }}, metadata) return tostring(node) end return tableutil