Edit in JSFiddle

//=============================================================================
// Sphinx-Spinner.js
//=============================================================================

/*:
 * @plugindesc Graphique sous forme de sprite
 * @author Sphinx
 * @help
 * //==========================================================================
 * // Plugin : Sphinx-Spinner
 * // Date   : 7 janvier 2020
 * // Auteur : Sphinx
 * //==========================================================================
 * Ce plugin permet de créer facilement un spinner comportant des valeurs
 * personnalisées au moyen de la nouvelle classe SphinxSpinner.
 * 
 * Usage :
 * spinner = new SphinxSpinner({
 *     width: 640, // Largeur du spinner
 *     height: 480, // Hauteur du spinner
 *     values: [ "Pile", "Face" ], // Valeurs à afficher dans la roue
 *     colors: [ "red", "blue" ], // Couleurs CSS du dégradé de la roue
 *     speed: {
 *         start: 1, // Vitesse initiale sur une échelle de 1 à 100
 *         max: 50, // Vitesse finale sur une échelle de 1 à 100
 *         acceleration: 5 // Pourcentage d'accélération de la roue
 *     }
 * });
 * 
 * Une fois le spinner créé, deux options se présentent :
 *     - Soit vous surveillez dans la méthode update de votre scène si le
 *       spinner s'est arrêté au moyen de la fonction :
 *           spinner.isStopped()
 *       puis vous récupérez sa valeur par le biais la propriété :
 *           spinner.value
 *     - Soit vous ajoutez un listener qui sera exécuté une et une seule fois
 *       lorsque le spinner s'arrête :
 *           spinner.setStopHandler(callback);
 *       La valeur du spinner sera passée à la fonction de callback.
 *       /!\ Si vous passez le contexte à la fonction de callback au moyen
 *       de la méthode callback.bind(this); pensez à passer aussi la référence
 *       au spinner au moyen de callback.bind(this, spinner); pour pouvoir
 *       récupérer la valeur du spinner.
 * 
 * Outre le spinner, la classe SphinxSpinner intègre une méthode statique qui
 * permet de récupérer un objet bitmap correspondant à l'ID d'une icône :
 * bitmap = SphinxSpinner.getIcon(id);
 * 
 * Dépendances : - Sphinx-Polyfill
 * 
 * @param arrow
 * @text Flèche du spinner
 * @type file
 * @default arrow
 * @dir img/system
 * @require 1
 * 
 * @param click
 * @text Icone de clic du spinner
 * @type file
 * @default click
 * @dir img/system
 * @require 1
 */
if(!Game_System.prototype.getColor) {
    Game_System.prototype.getColor = function(color) {
        d = document.createElement("div");
        d.style.color = color;
        document.body.appendChild(d);
        color = window.getComputedStyle(d).color;
        document.body.removeChild(d);
        color = color.replace(/^rgb\((\d+), (\d+), (\d+)\)$/, "$1-$2-$3").split("-");
        for(i = 0; i < color.length; ++i) {
            color[i] = parseInt(color[i], 10).toString(16);
            if(color[i].length == 0) color[i] = "00";
            else if(color[i].length == 1) color[i] = "0" + color[i];
        }
        return color.join("");
    };
}

Scene_Boot.sphinxSpinnerLoadSystemImages = Scene_Boot.loadSystemImages;
Scene_Boot.loadSystemImages = function() {
    Scene_Boot.sphinxSpinnerLoadSystemImages.call(this);
    ImageManager.reserveSystem(PluginManager.parameters("Sphinx-Spinner")["arrow"]);
    ImageManager.reserveSystem(PluginManager.parameters("Sphinx-Spinner")["click"]);
}

function SphinxSpinner() {
    this.initialize.apply(this, arguments);
};

SphinxSpinner.prototype = Object.create(Sprite_Base.prototype);
SphinxSpinner.prototype.constructor = SphinxSpinner;

SphinxSpinner.getIcon = function(iconIndex) {
    var bitmap = ImageManager.loadSystem('IconSet');
    var pw = Window_Base._iconWidth;
    var ph = Window_Base._iconHeight;
    var icon = new Bitmap(pw, ph);
    var sx = iconIndex % 16 * pw;
    var sy = Math.floor(iconIndex / 16) * ph;
    icon.blt(bitmap, sx, sy, pw, ph, 0, 0);
    return icon;
};

SphinxSpinner.prototype.initialize = function(config) {
    Sprite_Base.prototype.initialize.call(this);
    
    // Configuration
    this.config = config || {};
    this.width = config.width || 640;
    this.height = config.height || 420;
    this.config.values = this.config.values || [ "Pile", "Face" ];
    this.config.colors = this.config.colors || [ "red", "blue" ];
    this.config.speed = this.config.speed || {};
    this.config.speed.start = this.config.speed.start || 1;
    this.config.speed.max = this.config.speed.max || 50;
    this.config.speed.acceleration = this.config.speed.acceleration || 5;
    
    // Internal variables
    this.speed = this.config.speed.start / 100;
    this.touched = false;
    this.stopped = false;
    this.frames = 0;
    this.anchor.x = 0.5;
    this.anchor.y = 0.5;
    this.indexes = [];
    for(i = 0; i < this.config.values.length; ++i) {
        this.indexes.push(i);
    }
    
    // Spinner sprite
    this.spinner = new Sprite_Button();
    this.spinner.anchor.x = 0.5;
    this.spinner.anchor.y = 0.5;
    this.spinner.bitmap = new Bitmap(this.width, this.height);
    this.spinnerContext = this.spinner.bitmap._context;
    this.spinnerContext.translate(this.width / 2, this.height / 2);
    this.addChild(this.spinner);
    
    // Drawing
    this.drawClick();
    this.drawArrow();
    this.drawSpinner();
    
    // Click listener
    this.spinner.setClickHandler(SphinxSpinner.prototype.onTouch.bind(this));
};

SphinxSpinner.prototype.update = function() {
    Sprite_Base.prototype.update.call(this);
    
    // Rotation
    this.spinner.rotation -= this.speed;
    
    // Scale of hand
    if(this._click.bitmap) {
        if(this.isBusy()) this._click.opacity = 0;
        else this._click.opacity = 255;
        this._click.scale.x = 1 - 0.5 * this.frames / 60;
        this._click.scale.y = 1 - 0.5 * this.frames / 60;
        if(this.frames == 60) this.frames = 0;
        this.frames++;
    }
    
    // Acceleration and deceleration
    if(this.speed < this.config.speed.max / 100 && !this.touched) this.speed *= 1 + this.config.speed.acceleration / 100;
    if(this.speed >= this.config.speed.start / 100 && this.touched) this.speed /= 1 + this.config.speed.acceleration / 100;
    else if(this.speed < this.config.speed.start / 100 && !this.stopped) this.stop();
};

SphinxSpinner.prototype.setStopHandler = function(callback) {
    if(this.stopped) callback(this.value);
    else this.callback = callback;
};

SphinxSpinner.prototype.drawClick = function() {
    this._click = new Sprite_Base();
    this._click.anchor.x = 0.5;
    this._click.anchor.y = 0.5;
    this.addChild(this._click);
    clickBitmap = ImageManager.loadSystem(PluginManager.parameters("Sphinx-Spinner")["click"]);
    clickBitmap.addLoadListener((() => {
        scale = Math.min(this.width, this.height) * 1 / 3 / clickBitmap.width;
        this._click.bitmap = new Bitmap(clickBitmap.width * scale, clickBitmap.height * scale);
        this._click.bitmap.blt(clickBitmap, 0, 0, clickBitmap.width, clickBitmap.height, 0, 0, this._click.bitmap.width, this._click.bitmap.height);
    }).bind(this));
};

SphinxSpinner.prototype.drawSpinner = function() {
    this.indexes.shuffle();
    ray = Math.min(this.width, this.height) / 2 - 8;
    gradient = this.spinnerContext.createRadialGradient(0, 0, 0, 0, 0, ray);
    gradient.addColorStop(0, this.config.colors.shift());
    for(i = 0; i < this.config.colors.length; ++i) {
        gradient.addColorStop((i + 1) * 1 / this.config.colors.length, this.config.colors[i]);
    }
    for(i = 0; i < this.indexes.length; ++i) {
        v = this.config.values[this.indexes[i]];
        this.spinnerContext.beginPath(0, 0);
        this.spinnerContext.moveTo(0, 0);
        startAngle = i * 360 / this.indexes.length - 360 / 2 / this.indexes.length;
        nextAngle = i * 360 / this.indexes.length + 360 / 2 / this.indexes.length;
        this.spinnerContext.lineTo(ray * Math.cos(startAngle * Math.PI / 180), ray * Math.sin(startAngle * Math.PI / 180));
        this.spinnerContext.arc(0, 0, ray, startAngle * Math.PI / 180, nextAngle * Math.PI / 180);
        this.spinnerContext.lineWidth = 2;
        this.spinnerContext.strokeStyle = "black";
        this.spinnerContext.stroke();
        this.spinnerContext.save();
        this.spinnerContext.globalAlpha = 0.59765625;
        this.spinnerContext.fillStyle = gradient;
        this.spinnerContext.fill();
        this.spinnerContext.restore();
        this.spinnerContext.textAlign = "center";
        this.spinnerContext.textBaseline = "middle";
        this.spinnerContext.save();
        this.spinnerContext.rotate(i * 360 / this.indexes.length * Math.PI / 180);
        if(v instanceof Bitmap) {
            x1 = ray / 2 * Math.cos(startAngle * Math.PI / 180);
            y1 = ray / 2 * Math.sin(startAngle * Math.PI / 180);
            x2 = ray / 2 * Math.cos(nextAngle * Math.PI / 180);
            y2 = ray / 2 * Math.sin(nextAngle * Math.PI / 180);
            size = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) / 2;
            scale = size / Math.max(v.width, v.height);
            this.spinner.bitmap.blt(v, 0, 0, v.width, v.height, ray * 4 / 5 - v.width * scale, -v.height * scale / 2, v.width * scale, v.height * scale);
        } else {
            fontSize = ray / (this.config.values.length / 2);
            this.spinner.bitmap.fontSize = fontSize;
            while(this.spinner.bitmap.measureTextWidth(v.toString()) > ray / 2) {
                fontSize--;
                this.spinner.bitmap.fontSize = fontSize;
            }
            this.spinnerContext.font = this.spinner.bitmap._makeFontNameText();
            this.spinnerContext.fillText(v.toString(), ray * 3 / 4 - this.spinner.bitmap.measureTextWidth(v.toString()) / 4, 0);
        }
        this.spinnerContext.restore();
    }
};

SphinxSpinner.prototype.drawArrow = function() {
    this._arrow = new Sprite_Base();
    this._arrow.anchor.x = 0.5;
    this._arrow.anchor.y = 0.5;
    this._arrow.x = Math.min(this.width, this.height) / 2 - this._arrow.width;
    this.addChild(this._arrow);
    arrowBitmap = ImageManager.loadSystem(PluginManager.parameters("Sphinx-Spinner")["arrow"]);
    arrowBitmap.addLoadListener((() => {
        scale = Math.min(this.width, this.height) * 1 / 4 / clickBitmap.width;
        this._arrow.bitmap = new Bitmap(arrowBitmap.width * scale, arrowBitmap.height * scale);
        this._arrow.bitmap.blt(arrowBitmap, 0, 0, arrowBitmap.width, arrowBitmap.height, 0, 0, this._arrow.bitmap.width, this._arrow.bitmap.height);
    }).bind(this));
};

SphinxSpinner.prototype.isBusy = function() {
    return this.speed < this.config.speed.max / 100 || this.touched;
};

SphinxSpinner.prototype.isStopped = function() {
    return this.stopped;
};

SphinxSpinner.prototype.onTouch = function() {
    if(this.isBusy()) return;
    this.touched = true;
};

SphinxSpinner.prototype.stop = function() {
    this.stopped = true;
    this.speed = 0;
    i = Math.ceil((((this.spinner.rotation / Math.PI * 180) - 360 / 2 / this.config.values.length) % 360) / (360 / this.config.values.length)) * -1;
    this.value = this.indexes[i];
    if(this.callback) this.callback(this.value);
};