MagicArena Wiki
Advertisement

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
// ==========================================================================
// Start: Deck Charts
// Renders charts on deck articles
// Version 2.0.0
// Author: Aspallar
//
// ** Please dont edit this code directly in the wikia.
// ** Instead clone the git repository https://github.com/Aspallar/WikiLua
// <nowiki>
window.magicArena = window.magicArena || {};
window.magicArena.charts = window.magicArena.charts || (function ($) {
    /*globals google, mw, magicArena */
    'use strict';

    if (document.getElementById('mdw-deckcharts') === null || $('#mdw-disabled-js').attr('data-deckcharts-2-0-0'))
        return null;

    var colorPieChartId = 'mdw-cardsbycolor-chart',
        manaCurveChartId = 'mdw-manacurve-chart',
        typesPieChartId = 'mdw-types-chart',
        meanCmcId = 'mdw-mean-cmc',
        landProbabilitiesId = 'mdw-land-probabilities',
        lastManaCurveCmc = 6, // max cmc to show on mana curve chart

        colorColors = {
            Red: '#f28f78',
            Green: '#7dcd98',
            Blue: '#92d4f7',
            White: '#ffffd9',
            Black: '#515151',
            Multicolored: '#ffd778',
            Colorless: '#abafb0'
        },

        typeColors = {
            Land: '#FFFFFF',
            Creature: '#F5F5F5',
            Artifact: '#606060',
            Enchantment: '#696969',
            Instant: '#808080',
            Sorcery: '#B0B0B0',
            Planeswalker: '#C8C8C8'
        },

        dataIndex = {
            color: 0,
            num: 1,
            cmc: 2,
            type: 0
        },

        charts = {
            colorPie: {
                options: {
                    height: 240,
                    pieSliceText: 'value',
                    pieSliceBorderColor: 'black',
                    pieSliceTextStyle: {
                        color: 'black',
                        bold: true
                    },
                    backgroundColor: {
                        fill: 'transparent'
                    },
                    legend: {
                        textStyle: {
                            color: 'black'
                        }
                    }
                }
            },
            manaCurve: {
                selectedRow: null,
                options: {
                    height: 240,
                    legend: {
                        position: 'top',
                        maxLines: 3
                    },
                    bar: {
                        groupWidth: '80%'
                    },
                    isStacked: true,
                    backgroundColor: {
                        fill: 'transparent'
                    },
                    vAxis: { }
                },
                clearSelection: function () {
                    this.selectedRow = null;
                    this.chart.setSelection([]);
                },
                setSelection: function (row) {
                    this.chart.setSelection([{row:row, column:null}]);
                    this.selectedRow = row;
                }
            },
            typesPie: {
                options: {
                    height: 240,
                    pieSliceText: 'value',
                    pieSliceBorderColor: 'black',
                    pieSliceTextStyle: {
                        color: 'black',
                        bold: true
                    },
                    backgroundColor: {
                        fill: 'transparent'
                    },
                    legend: {
                        textStyle: {
                            color: 'black'
                        },
                        position: 'labeled'
                    }
                }
            }
        };

    function hasColorPieChart() {
        return document.getElementById(colorPieChartId) !== null;
    }

    function hasManaCurveChart() {
        return document.getElementById(manaCurveChartId) !== null;
    }

    function hasTypesPieChart() {
        return document.getElementById(typesPieChartId) !== null;
    }

    function hasMeanConvertedManaCost() {
        return document.getElementById(meanCmcId) !== null;
    }

    function hasLandProbabilities() {
        return document.getElementById(landProbabilitiesId) !== null;
    }

    function drawChart(chart) {
        if (chart.chart && chart.data)
            chart.chart.draw(chart.data, chart.options);
    }

    var statistics = {
        // n choose k
        nck: function (n, k) {
            var result = 1;
            for (var kcount = 1; kcount <= k; kcount++)
                result *= (n + 1 - kcount) / kcount;
            return result;
        },
        // probability mass function, hypergeometric distribution 
        pmf: function (N, m, n, k) {
            return (this.nck(m, k) * this.nck(N - m, n - k)) / this.nck(N, n);
        }
    };

    function findCard(deckData, sideboardData, name) {
        function nameMatch(c) { return c.name === name; }
        return deckData.find(nameMatch) || sideboardData.find(nameMatch);
    }

    function cmcClass(cmc) {
        return cmc >= lastManaCurveCmc ? 'mdw-cmc-max' : 'mdw-cmc-' + cmc;
    }

    function colorClass(color) {
        return 'mdw-card-' + color;
    }

    function clearHighlight() {
        $('.mdw-card-highlight').removeClass('mdw-card-highlight');
    }

    function onManaCurveSelect() {
        var mc = charts.manaCurve;
        clearHighlight();
        charts.colorPie.chart.setSelection([]);
        var selected = mc.chart.getSelection();
        if (selected.length > 0 && selected[0].column !== null) {
            var row = selected[0].row;
            if (row !== mc.selectedRow) {
                $('.' + cmcClass(row)).addClass('mdw-card-highlight');
                mc.setSelection(row);
            } else {
                mc.clearSelection();
            }
        }
    }

    function onColorPieSelect() {
        var cp = charts.colorPie;
        clearHighlight();
        var selected = cp.chart.getSelection();
        if (selected.length > 0) {
            var color = cp.data.getValue(selected[0].row, 0);
            $('.' + colorClass(color)).addClass('mdw-card-highlight');
            charts.manaCurve.clearSelection();
        }
    }

    function addHighlightClasses(deckData, sideboardData) {
        $('div.div-col.columns.column-count.column-count-2 span.card-image-tooltip').each(function () {
            var cardElement = $(this),
                card = findCard(deckData, sideboardData, cardElement.text());
            if (card) {
                if (typeof card.cmc === 'number')  
                    cardElement.addClass(cmcClass(card.cmc));
                if (typeof card.adjustedColor === 'string' && card.adjustedColor.length > 0)
                    cardElement.addClass(colorClass(card.adjustedColor));
            }
        });
    }

    function isLand(card) {
        return $.inArray('Land', card.types) !== -1;
    }

    function adjustedColor(colors) {
        if (colors === undefined || colors.length === 0)
            return 'Colorless';
        if (colors.length > 1)
            return 'Multicolored';
        return colors[0];
    }

    function normalName(name) {
        var pos = name.indexOf('//');
        return pos === -1 ? name : name.substring(0, pos - 1);
    }

    function addCalculatedFieldsToData(cardData) {
        cardData.forEach(function (card) {
            card.name = normalName(card.name);
            card.isLand = isLand(card);
            if (!card.isLand)
                card.adjustedColor = adjustedColor(card.colors);
        });
        return cardData;
    }

    function getData(id) {
        var dataElement = document.getElementById(id);
        var cardData = [];
        if (dataElement !== null) {
            try { cardData = JSON.parse(dataElement.textContent); } catch (e) { }
            return addCalculatedFieldsToData(cardData);
        }
        return cardData;
    }

    function hasNonLands(data) {
        return data.some(function (card) { return !card.isLand; });
    }

    function zeroedArray(size) {
        var arr = [];
        while (size--)
            arr.push(0);
        return arr;
    }

    function getCardTotals(cardData) {
        var totals = { cards: 0, lands: 0 };
        for (var k = 0, l = cardData.length; k < l; k++) {
            var card = cardData[k];
            totals.cards += card.num;
            if (card.isLand)
                totals.lands += card.num;
        }
        return totals;
    }

    function landProbabilities(cardTotals) {
        var result = [];
        for (var landCount = 0; landCount <= 7; landCount++) {
            var probability = statistics.pmf(cardTotals.cards, cardTotals.lands, 7, landCount);
            result.push(probability);
        }
        return result;
    }

    function meanConvertedManaCost(chartData) {
        var count = 0, totalCmc = 0;
        for (var k = 0, l = chartData.length; k < l; k++) {
            var card = chartData[k];
            if (!card.isLand) {
                count += card.num;
                totalCmc += card.cmc * card.num;
            }
        }
        return totalCmc / count;
    }

    function getColorOnlyData(cardData) {
        var colorData = [];
        cardData.forEach(function (card) {
            if (!card.isLand)
                colorData.push([card.adjustedColor, card.num]);
        });
        colorData.sort(function (a, b) {
            return a[dataIndex.color].localeCompare(b[dataIndex.color]);
        });
        return colorData;
    }

    function getCardTypeData(cardData) {
        var typeData = [];
        cardData.forEach(function (card) {
            card.types.forEach(function (type) {
                typeData.push([type, card.num]);
            });
        });
        typeData.sort(function (a, b) {
            return a[dataIndex.type].localeCompare(b[dataIndex.type]);
        });
        return typeData;
    }

    function sumData(data, groupBy) {

        function shouldSum(next, current) {
            return groupBy.every(function (i) { return current[i] === next[i]; });
        }
    
        var current, summedData = [];
        if (data.length === 0)
            return summedData;
        summedData.push(current = data[0]);
        for (var k = 1, l = data.length; k < l; k++) {
            var next = data[k];
            if (shouldSum(next, current))
                current[dataIndex.num] += next[dataIndex.num];
            else
                summedData.push(current = next);
        }
        return summedData;
    }

    function getColorWithCostData(cardData) {
        var colorData = [];
        cardData.forEach(function (card) {
            if (!card.isLand)
                colorData.push([card.adjustedColor, card.num, card.cmc]);
        });
        // order by color then by cmc
        colorData.sort(function (a, b) {
            var colorCompare = a[dataIndex.color].localeCompare(b[dataIndex.color]);
            if (colorCompare !== 0)
                return colorCompare;
            return a[dataIndex.cmc] - b[dataIndex.cmc];
        });
        return colorData;
    }

    function makeLabelsForManaCurve(chartData) {
        var labels = ['Cost'], k = 0, l = chartData.length;
        while (k < l) {
            var color = chartData[k++][dataIndex.color];
            labels.push(color);
            while (k < l && chartData[k][dataIndex.color] === color)
                ++k;
        }
        return labels;
    }

    function addZeroedDataSeriesForManaCurve(data, numSeries, seriesLength) {
        var labelIndex = 0;
        for (var k = 0; k < numSeries; k++) {
            var dataSeries = zeroedArray(seriesLength);
            dataSeries[labelIndex] = k.toString();
            data.push(dataSeries);
        }
        data[numSeries][labelIndex] += '+';
    }

    function fillDataForManaCurve(data, chartData, maxCmc) {
        var index = 1, color = chartData[0][dataIndex.color];
        chartData.forEach(function (row) {
            if (color !== row[dataIndex.color]) {
                ++index;
                color = row[dataIndex.color];
            }
            var cmc = Math.min(row[dataIndex.cmc], maxCmc);
            data[cmc + 1][index] += row[dataIndex.num];
        });
    }

    function nullZeroValuesInData(data, numSeries) {
        // we do this to avoid any zero values appearing in chart as a single line
        var totals = zeroedArray(numSeries);
        for (var seriesIndex = 1; seriesIndex < data.length; seriesIndex++) {
            var series = data[seriesIndex];
            for (var itemIndex = 1; itemIndex < series.length; itemIndex++) {
                totals[seriesIndex - 1] += series[itemIndex];
                if (series[itemIndex] === 0)
                    series[itemIndex] = null;
            }
        }
        return Math.max.apply(null, totals);
    }

    function getManaCurveAxisTicks(ticks, maxColumnTotal) {
        var tick = 0;
        do {
            tick += 2;
            ticks.push(tick);
        } while (tick < maxColumnTotal);
    }

    function shapeDataForManaCurveChart(chartData, ticks) {
        var lastCmc = lastManaCurveCmc + 1,
            labels = makeLabelsForManaCurve(chartData),
            data = [labels];
        addZeroedDataSeriesForManaCurve(data, lastCmc, labels.length);
        fillDataForManaCurve(data, chartData, lastCmc - 1);
        var maxColumnTotal = nullZeroValuesInData(data, lastCmc);
        getManaCurveAxisTicks(ticks, maxColumnTotal);
        return data;
    }

    function cacheManaCurveData(data) {
        var summedData = sumData(getColorWithCostData(data), [dataIndex.color, dataIndex.cmc]),
            ticks = [],
            shapedData = shapeDataForManaCurveChart(summedData, ticks),
            sectionColors = [];

        for (var k = 1; k < shapedData[0].length; k++)
            sectionColors.push(colorColors[shapedData[0][k]]);

        var mc = charts.manaCurve;
        mc.data = google.visualization.arrayToDataTable(shapedData);
        mc.options.colors = sectionColors;
        mc.options.vAxis.ticks = ticks;
    }

    function cacheColorPieData(cardData) {
        var summedData = sumData(getColorOnlyData(cardData), [dataIndex.color]),
            dt = new google.visualization.DataTable();

        dt.addColumn('string', 'Color');
        dt.addColumn('number', 'Amount');
        dt.addRows(summedData);

        var sliceColors = summedData.map(function (e) {
            return colorColors[e[dataIndex.color]];
        });

        var cp = charts.colorPie;
        cp.data = dt;
        cp.options.colors = sliceColors;
    }

    function cacheTypesPieData(cardData) {
        var summedData = sumData(getCardTypeData(cardData), [dataIndex.type]),
            dt = new google.visualization.DataTable();

        dt.addColumn('string', 'Type');
        dt.addColumn('number', 'Amount');
        dt.addRows(summedData);

        var sliceColors = summedData.map(function (e) {
            return typeColors[e[dataIndex.type]];
        });

        var tp = charts.typesPie;
        tp.data = dt;
        tp.options.colors = sliceColors;
    }

    function createCharts() {
        var container = document.getElementById(typesPieChartId);
        if (container  !== null) {
            charts.typesPie.chart = new google.visualization.PieChart(container);
        }
        container = document.getElementById(colorPieChartId);
        if (container !== null) {
            charts.colorPie.chart = new google.visualization.PieChart(container);
            google.visualization.events.addListener(charts.colorPie.chart, 'select', onColorPieSelect);
        }
        container = document.getElementById(manaCurveChartId);
        if (container !== null) {
            charts.manaCurve.chart = new google.visualization.ColumnChart(container);
            google.visualization.events.addListener(charts.manaCurve.chart, 'select', onManaCurveSelect);
        }
    }

    function setMeanCmc(chartData) {
        var mean = meanConvertedManaCost(chartData).toFixed(2);
        $('#' + meanCmcId).text(mean);
    }

    var landProbabilityTemplate;
    function getLandProbabilityTemplate() {
        landProbabilityTemplate = $('#' + landProbabilitiesId).html();
    }

    function setLandDrawProbabilities(chartData) {
        var probabilities = landProbabilities(getCardTotals(chartData));
        var html = landProbabilityTemplate;
        for (var k = 0; k <= 7; k++) {
            var percent = (probabilities[k] * 100).toFixed(1);
            html = html.replace('[' + k + ']', percent);
        }
        $('#' + landProbabilitiesId).html(html);
    }

    function drawAllCharts() {
        if (hasColorPieChart())
            drawChart(charts.colorPie);
        if (hasManaCurveChart())
            drawChart(charts.manaCurve);
        if (hasTypesPieChart())
            drawChart(charts.typesPie);
    }

    function setAllNonChartSections(chartData) {
        if (hasMeanConvertedManaCost())
            setMeanCmc(chartData);
        if (hasLandProbabilities())
            setLandDrawProbabilities(chartData);
    }

    function wireEvents() {
        $('.mdw-charts-more-button').click(function () {
            $('#mdw-misc-stats-contents').toggle(500);
        });
        $(window).resize(magicArena.utils.debounce(drawAllCharts, 150));
    }

    function changeMoreSpansToButtons() {
        $('.mdw-charts-more-button').replaceWith(function () {
            return $('<input type="button" class="mdw-charts-more-button mdw-chart-box-button" />')
                .val($(this).text());
        });
    }

    function setVisible(visible) {
        $('#mdw-deckcharts-container').toggle(visible);
        $('#mdw-deckcharts-error').toggle(!visible);
    }

    function refresh() {
        var data = getData('mdw-chartdata-pre');
        if (hasNonLands(data)) {
            setVisible(true);
            addHighlightClasses(data, getData('mdw-sideboard-data'));
            cacheColorPieData(data);
            cacheManaCurveData(data);
            cacheTypesPieData(data);
            drawAllCharts();
            setAllNonChartSections(data);
        } else {
            setVisible(false);
        }
    }

    function chartLibraryLoaded() {
        getLandProbabilityTemplate();
        createCharts();
        refresh();
        changeMoreSpansToButtons();
        wireEvents();
        mw.hook('magicarena.chartsready').fire();
    }

    $.getScript('https://www.gstatic.com/charts/loader.js', function () {
        google.charts.load('current', { 'packages': ['corechart'] });
        google.charts.setOnLoadCallback(chartLibraryLoaded);
    });

    return {
        refresh: refresh
    };

})(jQuery);
Advertisement