<commit_before>document.addEventListener("DOMContentLoaded", function() {
    initVariables();
    initObject();

    loadFromLocalStorage();
});

var audioplayer = {
    version: "0.0.2",
    name: "Audioplayer",

    newSong: function(aElement) {
        audioplayer.stop();

        try {
            document.querySelector("[data-type='audio'].active").className = document.querySelector("[data-type='audio'].active").className.replace("active", "");
        } catch (error) {
            // no element was active
        }
        aElement.className += "active";

        // get song info
        var path = aElement.getAttribute("data-path");
        var title = aElement.getElementsByClassName("title")[0];
        var artist = aElement.getElementsByClassName("artist")[0];
        var album = aElement.getElementsByClassName("album")[0];
        var duration = aElement.getElementsByClassName("duration")[0];
        var coverImage = aElement.getElementsByTagName("img")[0].getAttribute("src");

        // set new info
        audioplayer.elements.title.innerHTML = title.innerHTML;

        if (typeof artist != "undefined") {
            audioplayer.elements.artist.innerHTML = artist.innerHTML;
        } else {
            audioplayer.elements.artist.innerHTML = null;
        }

        if (typeof album != "undefined") {
            audioplayer.elements.album.innerHTML = album.innerHTML;
        } else {
            audioplayer.elements.album.innerHTML = null;
        }

        audioplayer.elements.cover.setAttribute("src", coverImage);
        audioplayer.elements.timeline.total.innerHTML = duration.innerHTML;

        // test if browser supports audio format
        var extSplit = path.split(".");
        var format = extSplit[extSplit.length - 1];

        if (!! audioplayer.audio.object.canPlayType("audio/" + format) === false) {

            swal("Formato no soportado", "No es posible reproducir la cancion, tu navegador no soporta el formato " + format.toUpperCase(), "error");

        } else {
            // start new object
            audioplayer.audio.object = new Audio(path);
            audioplayer.play();

            audioplayer.audio.object.addEventListener("timeupdate", audioplayer.timeUpdate, false);
        }

    },
    play: function() {
        audioplayer.audio.object.volume = audioplayer.audio.volume;
        audioplayer.audio.object.muted = audioplayer.audio.muted;

        audioplayer.audio.object.play();
        //audioplayer.audio.object.onended = audioplayer.onended();

        // update document title
        // update doument icon

        // scroll to active song

        audioplayer.elements.buttons.play.style.display = "none";
        audioplayer.elements.buttons.pause.style.display = "inline-block";

    },
    pause: function() {
        audioplayer.audio.object.pause();

        // set default document title

        audioplayer.elements.buttons.pause.style.display = "none";
        audioplayer.elements.buttons.play.style.display = "inline-bloc";
    },
    stop: function() {
        audioplayer.pause();
        audioplayer.audio.object.currentTime = 0;
        audioplayer.elements.timeline.input.style.marginLeft = "0px";
    },
    nextSong: function() {
        var active = parseInt(document.querySelector("#songs .active").getAttribute("data-num"));
        var totalSongs = parseInt(document.querySelectorAll("#songs [data-type='audio']").length);

        // $loadOn = $totalSongs - $loadAfter;

        if (active < totalSongs) {
            var next = document.querySelector("#songs [data-num='" + (active + 1) + "']");
            audioplayer.newSong(next);

            // load more songs via ajax

        } else {
            swal({
                title:'Siguiente canción no disponible',
                text:'No hay mas canciones que reproducir',
                type:'info',
                showConfirmButton: false,
                timer: audioplayer.config.swalTimer
            });
        }
    },
    previousSong: function() {
        var active = parseInt(document.querySelector("#songs .active").getAttribute("data-num"));

        if (active > 0) {
            var previous = document.querySelector("#songs [data-num='" + (active - 1)  + "']");

            audioplayer.newSong(previous);

        } else {
            swal({
                title: '¡Canción anterior no disponible!',
                text: 'No hay ninguna cancion anterior a la actual',
                type: 'error',
                showConfirmButton: false,
                timer: audioplayer.config.swalTimer
            });
        }
    },
    repeat: function(repeat) {
        var mode = repeat.attr("data-mode");
        var button;

        repeat.style.display = "none";

        switch (mode) {
            case "none":
                mode = "all";
                button = audioplayer.elements.buttons.repeatAll;
                break;
            case "all":
                mode = "one";
                button = audioplayer.elements.buttons.repeatOne;
                break;
            case "one":
                mode = "none";
                button = audioplayer.elements.buttons.repeatNone;
                break;
        }

        audioplayer.audio.repeat = mode;
        button.style.display = "inline-block";
    },
    volumeUp: function() {
        if (audioplayer.audio.volume < 1) {
            var newVolume = Math.round((audioplayer.audio.volume + 0.02) * 100) / 100;

            audioplayer.audio.object.volume = newVolume;
            audioplayer.audio.volume = newVolume;

            audioplayer.elements.buttons.volumeIndicator.setAttribute("data-volume", Math.ceil(audioplayer.audio.volume * 100));

            if (audioplayer.audio.volume >= audioplayer.audio.volumeUpValue && audioplayer.audio.muted == false) {
                audioplayer.elements.buttons.volumeUp.style.display = "inline-block";
                audioplayer.elements.buttons.volumeDown.style.display = "none";

            } else if (audioplayer.audio.volume < audioplayer.audio.volumeUpValue && audioplayer.audio.muted == false) {
                audioplayer.elements.buttons.volumeDown.style.display = "inline-block";
                audioplayer.elements.buttons.volumeUp.style.display = "none";
            }

            localStorage.audioVolume = audioplayer.audio.volume * 100;
        }
    },
    volumeDown: function() {
        if (audioplayer.audio.volume > 0) {
            var newVolume = Math.round((audioplayer.audio.volume - 0.02) * 100) / 100;

            audioplayer.audio.object.volume = newVolume;
            audioplayer.audio.volume = newVolume;

            audioplayer.elements.buttons.volumeIndicator.setAttribute("data-volume", Math.ceil(audioplayer.audio.volume * 100));

            if (audioplayer.audio.volume >= audioplayer.audio.volumeUpValue && audioplayer.audio.muted == false) {
                audioplayer.elements.buttons.volumeUp.style.display = "inline-block";
                audioplayer.elements.buttons.volumeDown.style.display = "none";

            } else if (audioplayer.audio.volume < audioplayer.audio.volumeUpValue && audioplayer.audio.muted == false) {
                audioplayer.elements.buttons.volumeDown.style.display = "inline-block";
                audioplayer.elements.buttons.volumeUp.style.display = "none";
            }

            localStorage.audioVolume = audioplayer.audio.volume * 100;
        }
    },
    volumeMute: function() {
        if (!audioplayer.audio.muted) {
            audioplayer.audio.object.muted = true;
            audioplayer.audio.muted = true;

            audioplayer.elements.buttons.volumeUp.style.display = "none";
            audioplayer.elements.buttons.volumeDown.style.display = "none";
            audioplayer.elements.buttons.volumeMute.style.display = "inline-block";

        } else {
            audioplayer.audio.object.muted = false;
            audioplayer.audio.muted = false;

            audioplayer.elements.buttons.volumeMute.style.display = "none";

            if (audioplayer.audio.volume > audioplayer.audio.volumeUpValue) {
                audioplayer.elements.buttons.volumeUp.style.display = "inline-block";
            } else {
                audioplayer.elements.buttons.volumeDown.style.display = "inline-block";
            }
        }
    },
    onended: function() {
        audioplayer.stop();

        switch (audioplayer.audio.repeat) {
            case "all":
                audioplayer.nextSong();
                break;
            case "one":

        }
    },
    timeUpdate: function() {
        var percent = (audioplayer.audio.object.currentTime / audioplayer.audio.object.duration) * 100;
        audioplayer.elements.timeline.input.style.left = "calc(" + percent + "% - 5px)";

        var curTime = Math.round(audioplayer.audio.object.currentTime);

        var curMin = 0;
        var curSec = 0;

        if (curTime < 60) {
            curSec = curTime;
        } else {
            curMin = Math.floor(curTime / 60);
            curSec = curTime - curMin * 60;
        }

        if (curMin < 10) {
            curMin = "0" + curMin;
        }
        if (curSec < 10) {
            curSec = "0" + curSec;
        }

        audioplayer.elements.timeline.played.innerHTML = curMin + ":" + curSec;
    },

    log: function(txt) {
        console.info(audioplayer.name + " v" + audioplayer.version + ": " + txt);
    }
};

// audioplayer HTML elements
function initVariables() {
    audioplayer.elements = {};

    audioplayer.elements.player = document.getElementById("audioplayer");
    audioplayer.elements.title = audioplayer.elements.player.getElementsByClassName("title")[0];
    audioplayer.elements.artist = audioplayer.elements.player.getElementsByClassName("artist")[0].getElementsByClassName("content")[0];
    audioplayer.elements.album = audioplayer.elements.player.getElementsByClassName("album")[0].getElementsByClassName("content")[0];
    audioplayer.elements.cover = audioplayer.elements.player.getElementsByTagName("img")[0];

    audioplayer.elements.buttons = {};

    audioplayer.elements.buttons.play = audioplayer.elements.player.querySelector("[name='play']");
    audioplayer.elements.buttons.pause = audioplayer.elements.player.querySelector("[name='pause']");

    audioplayer.elements.buttons.stop = audioplayer.elements.player.querySelector("[name='stop']");

    audioplayer.elements.buttons.previousSong = audioplayer.elements.player.querySelector("[name='before']");
    audioplayer.elements.buttons.nextSong = audioplayer.elements.player.querySelector("[name='after']");

    audioplayer.elements.buttons.repeat = audioplayer.elements.player.querySelector("[name='repeat']");
    audioplayer.elements.buttons.repeatNone = audioplayer.elements.player.querySelector("[name='repeat'][data-mode='none']");
    audioplayer.elements.buttons.repeatAll = audioplayer.elements.player.querySelector("[name='repeat'][data-mode='all']");
    audioplayer.elements.buttons.repeatOne = audioplayer.elements.player.querySelector("[name='repeat'][data-mode='one']");

    audioplayer.elements.buttons.volumeIndicator = audioplayer.elements.player.querySelector("[name='volume']");
    audioplayer.elements.buttons.volumeUp = audioplayer.elements.player.querySelector("[name='volume'][data-mode='up']");
    audioplayer.elements.buttons.volumeDown = audioplayer.elements.player.querySelector("[name='volume'][data-mode='down']");
    audioplayer.elements.buttons.volumeMute = audioplayer.elements.player.querySelector("[name='volume'][data-mode='mute']");

    audioplayer.elements.timeline = {};

    audioplayer.elements.timeline.timeline = audioplayer.elements.player.getElementsByClassName("range")[0];
    audioplayer.elements.timeline.input = audioplayer.elements.player.getElementsByClassName("input")[0];
    audioplayer.elements.timeline.played = audioplayer.elements.player.getElementsByClassName("begin")[0];
    audioplayer.elements.timeline.total = audioplayer.elements.player.getElementsByClassName("end")[0];

    audioplayer.log("Variabled loaded");
}

// audio objects & properties
function initObject() {
     audioplayer.audio = {};

     audioplayer.audio.object = new Audio();

     audioplayer.audio.currentTime = audioplayer.audio.object.currentTime;
     audioplayer.audio.volume = 1;
     audioplayer.audio.repeat = "none";
     audioplayer.audio.muted = false;
     audioplayer.audio.volumeUpValue = 0.5;

     audioplayer.config = {};

     audioplayer.config.moreSongs = true;
     audioplayer.config.folder = "/";
     audioplayer.config.loadAfter = 30;
     audioplayer.config.swalTimer = 1500;
     audioplayer.config.scrollAnimationDuration = 500;
     audioplayer.config.scrollEnabled = true;

     audioplayer.log("Object and Properties loaded");
}

function loadFromLocalStorage() {
    var lsAudioVolume = localStorage.audioVolume;
    if (typeof lsAudioVolume == "undefined") {
        localStorage.audioVolume = audioplayer.audio.volume * 100;
    } else {
        audioplayer.audio.volume = parseInt(lsAudioVolume) / 100;
    }
    audioplayer.elements.buttons.volumeIndicator.setAttribute("data-volume", Math.floor(audioplayer.audio.volume * 100));

    var lsAudioRepeat = localStorage.audioRepeat;
    if (typeof lsAudioRepeat == "undefined") {
        localStorage.audioRepeat = audioplayer.audio.repeat;
    } else {
        audioplayer.audio.repeat = lsAudioRepeat;
    }

    audioplayer.elements.buttons.repeat.style.display = "none";

    switch(audioplayer.audio.repeat) {
        case "all":
            audioplayer.elements.buttons.repeatAll.style.display = "inline-block";
            break;
        case "one":
            audioplayer.elements.buttons.repeatOne.style.display = "inline-block";
            break;
        case "none":
            audioplayer.elements.buttons.repeaNone.style.display = "inline-block";
            break;
    }
}

var hotKeys;
require("electron").ipcRenderer.on("keyPress", (event, action) => {

    //audioplayer.hotKeys(action);

});
<commit_msg>Added scroll animation<commit_after>document.addEventListener("DOMContentLoaded", function() {
    initVariables();
    initObject();

    loadFromLocalStorage();
});

var audioplayer = {
    version: "0.0.2",
    name: "Audioplayer",

    newSong: function(aElement) {
        audioplayer.stop();

        try {
            document.querySelector("[data-type='audio'].active").className = document.querySelector("[data-type='audio'].active").className.replace("active", "");
        } catch (error) {
            // no element was active
        }
        aElement.className += "active";

        // get song info
        var path = aElement.getAttribute("data-path");
        var title = aElement.getElementsByClassName("title")[0];
        var artist = aElement.getElementsByClassName("artist")[0];
        var album = aElement.getElementsByClassName("album")[0];
        var duration = aElement.getElementsByClassName("duration")[0];
        var coverImage = aElement.getElementsByTagName("img")[0].getAttribute("src");

        // set new info
        audioplayer.elements.title.innerHTML = title.innerHTML;

        if (typeof artist != "undefined") {
            audioplayer.elements.artist.innerHTML = artist.innerHTML;
        } else {
            audioplayer.elements.artist.innerHTML = null;
        }

        if (typeof album != "undefined") {
            audioplayer.elements.album.innerHTML = album.innerHTML;
        } else {
            audioplayer.elements.album.innerHTML = null;
        }

        audioplayer.elements.cover.setAttribute("src", coverImage);
        audioplayer.elements.timeline.total.innerHTML = duration.innerHTML;

        // test if browser supports audio format
        var extSplit = path.split(".");
        var format = extSplit[extSplit.length - 1];

        if (!! audioplayer.audio.object.canPlayType("audio/" + format) === false) {

            swal("Formato no soportado", "No es posible reproducir la cancion, tu navegador no soporta el formato " + format.toUpperCase(), "error");

        } else {
            // start new object
            audioplayer.audio.object = new Audio(path);
            audioplayer.play();

            audioplayer.audio.object.addEventListener("timeupdate", audioplayer.timeUpdate, false);
        }

    },
    play: function() {
        audioplayer.audio.object.volume = audioplayer.audio.volume;
        audioplayer.audio.object.muted = audioplayer.audio.muted;

        audioplayer.audio.object.play();
        //audioplayer.audio.object.onended = audioplayer.onended();

        // update document title
        // update doument icon

        audioplayer.scroll(document.getElementById("songs"), document.querySelector("#songs .active"), audioplayer.config.scrollAnimationDuration);

        audioplayer.elements.buttons.play.style.display = "none";
        audioplayer.elements.buttons.pause.style.display = "inline-block";

    },
    pause: function() {
        audioplayer.audio.object.pause();

        // set default document title

        audioplayer.elements.buttons.pause.style.display = "none";
        audioplayer.elements.buttons.play.style.display = "inline-block";
    },
    stop: function() {
        audioplayer.pause();
        audioplayer.audio.object.currentTime = 0;
        audioplayer.elements.timeline.input.style.marginLeft = "0px";
    },
    nextSong: function() {
        var active = parseInt(document.querySelector("#songs .active").getAttribute("data-num"));
        var totalSongs = parseInt(document.querySelectorAll("#songs [data-type='audio']").length);

        // $loadOn = $totalSongs - $loadAfter;

        if (active < totalSongs) {
            var next = document.querySelector("#songs [data-num='" + (active + 1) + "']");
            audioplayer.newSong(next);

            // load more songs via ajax

        } else {
            swal({
                title:'Siguiente canción no disponible',
                text:'No hay mas canciones que reproducir',
                type:'info',
                showConfirmButton: false,
                timer: audioplayer.config.swalTimer
            });
        }
    },
    previousSong: function() {
        var active = parseInt(document.querySelector("#songs .active").getAttribute("data-num"));

        if (active > 0) {
            var previous = document.querySelector("#songs [data-num='" + (active - 1)  + "']");

            audioplayer.newSong(previous);

        } else {
            swal({
                title: '¡Canción anterior no disponible!',
                text: 'No hay ninguna cancion anterior a la actual',
                type: 'error',
                showConfirmButton: false,
                timer: audioplayer.config.swalTimer
            });
        }
    },
    repeat: function(repeat) {
        var mode = repeat.attr("data-mode");
        var button;

        repeat.style.display = "none";

        switch (mode) {
            case "none":
                mode = "all";
                button = audioplayer.elements.buttons.repeatAll;
                break;
            case "all":
                mode = "one";
                button = audioplayer.elements.buttons.repeatOne;
                break;
            case "one":
                mode = "none";
                button = audioplayer.elements.buttons.repeatNone;
                break;
        }

        audioplayer.audio.repeat = mode;
        button.style.display = "inline-block";
    },
    volumeUp: function() {
        if (audioplayer.audio.volume < 1) {
            var newVolume = Math.round((audioplayer.audio.volume + 0.02) * 100) / 100;

            audioplayer.audio.object.volume = newVolume;
            audioplayer.audio.volume = newVolume;

            audioplayer.elements.buttons.volumeIndicator.setAttribute("data-volume", Math.ceil(audioplayer.audio.volume * 100));

            if (audioplayer.audio.volume >= audioplayer.audio.volumeUpValue && audioplayer.audio.muted == false) {
                audioplayer.elements.buttons.volumeUp.style.display = "inline-block";
                audioplayer.elements.buttons.volumeDown.style.display = "none";

            } else if (audioplayer.audio.volume < audioplayer.audio.volumeUpValue && audioplayer.audio.muted == false) {
                audioplayer.elements.buttons.volumeDown.style.display = "inline-block";
                audioplayer.elements.buttons.volumeUp.style.display = "none";
            }

            localStorage.audioVolume = audioplayer.audio.volume * 100;
        }
    },
    volumeDown: function() {
        if (audioplayer.audio.volume > 0) {
            var newVolume = Math.round((audioplayer.audio.volume - 0.02) * 100) / 100;

            audioplayer.audio.object.volume = newVolume;
            audioplayer.audio.volume = newVolume;

            audioplayer.elements.buttons.volumeIndicator.setAttribute("data-volume", Math.ceil(audioplayer.audio.volume * 100));

            if (audioplayer.audio.volume >= audioplayer.audio.volumeUpValue && audioplayer.audio.muted == false) {
                audioplayer.elements.buttons.volumeUp.style.display = "inline-block";
                audioplayer.elements.buttons.volumeDown.style.display = "none";

            } else if (audioplayer.audio.volume < audioplayer.audio.volumeUpValue && audioplayer.audio.muted == false) {
                audioplayer.elements.buttons.volumeDown.style.display = "inline-block";
                audioplayer.elements.buttons.volumeUp.style.display = "none";
            }

            localStorage.audioVolume = audioplayer.audio.volume * 100;
        }
    },
    volumeMute: function() {
        if (!audioplayer.audio.muted) {
            audioplayer.audio.object.muted = true;
            audioplayer.audio.muted = true;

            audioplayer.elements.buttons.volumeUp.style.display = "none";
            audioplayer.elements.buttons.volumeDown.style.display = "none";
            audioplayer.elements.buttons.volumeMute.style.display = "inline-block";

        } else {
            audioplayer.audio.object.muted = false;
            audioplayer.audio.muted = false;

            audioplayer.elements.buttons.volumeMute.style.display = "none";

            if (audioplayer.audio.volume > audioplayer.audio.volumeUpValue) {
                audioplayer.elements.buttons.volumeUp.style.display = "inline-block";
            } else {
                audioplayer.elements.buttons.volumeDown.style.display = "inline-block";
            }
        }
    },
    onended: function() {
        audioplayer.stop();

        switch (audioplayer.audio.repeat) {
            case "all":
                audioplayer.nextSong();
                break;
            case "one":

        }
    },
    timeUpdate: function() {
        var percent = (audioplayer.audio.object.currentTime / audioplayer.audio.object.duration) * 100;
        audioplayer.elements.timeline.input.style.left = "calc(" + percent + "% - 5px)";

        var curTime = Math.round(audioplayer.audio.object.currentTime);

        var curMin = 0;
        var curSec = 0;

        if (curTime < 60) {
            curSec = curTime;
        } else {
            curMin = Math.floor(curTime / 60);
            curSec = curTime - curMin * 60;
        }

        if (curMin < 10) {
            curMin = "0" + curMin;
        }
        if (curSec < 10) {
            curSec = "0" + curSec;
        }

        audioplayer.elements.timeline.played.innerHTML = curMin + ":" + curSec;
    },

    scroll: function(parent, element, duration) {
        if (duration <= 0) {
            return
        }

        // mouse not over parent
        if (parent.querySelector(":hover") === null) {
            if (typeof element == "object") {
                var px = parent.scrollTop + element.getBoundingClientRect().top - element.clientHeight / 2;
            } else {
                var px = element;
            }

            var diff = px - parent.scrollTop;
            var perTick = diff / duration * 10;

            setTimeout(function () {
                parent.scrollTop = parent.scrollTop + perTick;
                if (parent.scrollTop == px) {
                    return;
                }
                audioplayer.scroll(parent, px, duration -10);
            }, 10);
        }
    },

    log: function(txt) {
        console.info(audioplayer.name + " v" + audioplayer.version + ": " + txt);
    }
};

// audioplayer HTML elements
function initVariables() {
    audioplayer.elements = {};

    audioplayer.elements.player = document.getElementById("audioplayer");
    audioplayer.elements.title = audioplayer.elements.player.getElementsByClassName("title")[0];
    audioplayer.elements.artist = audioplayer.elements.player.getElementsByClassName("artist")[0].getElementsByClassName("content")[0];
    audioplayer.elements.album = audioplayer.elements.player.getElementsByClassName("album")[0].getElementsByClassName("content")[0];
    audioplayer.elements.cover = audioplayer.elements.player.getElementsByTagName("img")[0];

    audioplayer.elements.buttons = {};

    audioplayer.elements.buttons.play = audioplayer.elements.player.querySelector("[name='play']");
    audioplayer.elements.buttons.pause = audioplayer.elements.player.querySelector("[name='pause']");

    audioplayer.elements.buttons.stop = audioplayer.elements.player.querySelector("[name='stop']");

    audioplayer.elements.buttons.previousSong = audioplayer.elements.player.querySelector("[name='before']");
    audioplayer.elements.buttons.nextSong = audioplayer.elements.player.querySelector("[name='after']");

    audioplayer.elements.buttons.repeat = audioplayer.elements.player.querySelector("[name='repeat']");
    audioplayer.elements.buttons.repeatNone = audioplayer.elements.player.querySelector("[name='repeat'][data-mode='none']");
    audioplayer.elements.buttons.repeatAll = audioplayer.elements.player.querySelector("[name='repeat'][data-mode='all']");
    audioplayer.elements.buttons.repeatOne = audioplayer.elements.player.querySelector("[name='repeat'][data-mode='one']");

    audioplayer.elements.buttons.volumeIndicator = audioplayer.elements.player.querySelector("[name='volume']");
    audioplayer.elements.buttons.volumeUp = audioplayer.elements.player.querySelector("[name='volume'][data-mode='up']");
    audioplayer.elements.buttons.volumeDown = audioplayer.elements.player.querySelector("[name='volume'][data-mode='down']");
    audioplayer.elements.buttons.volumeMute = audioplayer.elements.player.querySelector("[name='volume'][data-mode='mute']");

    audioplayer.elements.timeline = {};

    audioplayer.elements.timeline.timeline = audioplayer.elements.player.getElementsByClassName("range")[0];
    audioplayer.elements.timeline.input = audioplayer.elements.player.getElementsByClassName("input")[0];
    audioplayer.elements.timeline.played = audioplayer.elements.player.getElementsByClassName("begin")[0];
    audioplayer.elements.timeline.total = audioplayer.elements.player.getElementsByClassName("end")[0];

    audioplayer.log("Variabled loaded");
}

// audio objects & properties
function initObject() {
     audioplayer.audio = {};

     audioplayer.audio.object = new Audio();

     audioplayer.audio.currentTime = audioplayer.audio.object.currentTime;
     audioplayer.audio.volume = 1;
     audioplayer.audio.repeat = "none";
     audioplayer.audio.muted = false;
     audioplayer.audio.volumeUpValue = 0.5;

     audioplayer.config = {};

     audioplayer.config.moreSongs = true;
     audioplayer.config.folder = "/";
     audioplayer.config.loadAfter = 30;
     audioplayer.config.swalTimer = 1500;
     audioplayer.config.scrollAnimationDuration = 500;
     audioplayer.config.scrollEnabled = true;

     audioplayer.log("Object and Properties loaded");
}

function loadFromLocalStorage() {
    var lsAudioVolume = localStorage.audioVolume;
    if (typeof lsAudioVolume == "undefined") {
        localStorage.audioVolume = audioplayer.audio.volume * 100;
    } else {
        audioplayer.audio.volume = parseInt(lsAudioVolume) / 100;
    }
    audioplayer.elements.buttons.volumeIndicator.setAttribute("data-volume", Math.floor(audioplayer.audio.volume * 100));

    var lsAudioRepeat = localStorage.audioRepeat;
    if (typeof lsAudioRepeat == "undefined") {
        localStorage.audioRepeat = audioplayer.audio.repeat;
    } else {
        audioplayer.audio.repeat = lsAudioRepeat;
    }

    audioplayer.elements.buttons.repeat.style.display = "none";

    switch(audioplayer.audio.repeat) {
        case "all":
            audioplayer.elements.buttons.repeatAll.style.display = "inline-block";
            break;
        case "one":
            audioplayer.elements.buttons.repeatOne.style.display = "inline-block";
            break;
        case "none":
            audioplayer.elements.buttons.repeaNone.style.display = "inline-block";
            break;
    }
}

var hotKeys;
require("electron").ipcRenderer.on("keyPress", (event, action) => {

    //audioplayer.hotKeys(action);

});
<|endoftext|>


<commit_before>Autocompleter.JSONSearch = Class.create(Autocompleter.Local, {

  patterns: {
    infix: '(.*?)($token)(.*?)',
    prefix: '^($token)(.*?)',
    exact: '^($token)$',
    word: '(.*?\\b)($token)(\\b.*?)',
    word_prefix: "^(.*?\\b)($token)(.*?)$"
  },

  initialize: function($super, element, update, data, options) {
    this.options = (options || {});
    this.format_options = this.extractFormatOptions();
    this.search_options = this.extractSearchOptions();
    this.setupJSONSearch();
    this.setupSearchSelector();
    $super(element, update, data, options);
    if (this.options.focus) {
      $(element).focus();
    }
  },

  setupJSONSearch: function() {
    this.json_search = new JSONSearch(this.search_options);
  },

  extractSearchOptions: function() {
    var search_options = Object.clone(this.options.search_options || {});
    if (Object.isUndefined(search_options.limit)) {
      search_options.limit = (this.options.choices || 10);
    }
    if (Object.isUndefined(search_options.case_sensitive)) {
      search_options.case_sensitive = !!this.options.ignoreCase;
    }
    return search_options;
  },

  extractFormatOptions: function() {
    var format_options = Object.clone(this.options.format_options);
    if (!format_options.order) {
      format_options.order = Object.keys(this.search_options.fields);
    }
    return format_options;
  },

  setupSearchSelector: function() {
    this.options.selector = function(instance) {
      var token = instance.getToken();
      var results = instance.json_search.getResults(token, instance.options.array);
      return instance.parseResults(results, token);
    }
  },

  parseResults: function(results, token) {
    var formatted_fields = [], formatted_results = [], self = this;
    results._each(function(result) {
      self.format_options.order._each(function(field) {
        formatted_fields.push(self.parseResult(result[field], token, field));
      });
      formatted_results.push(self.wrapFields(formatted_fields.compact().join("\n"), result));
    });
    return ('
' + formatted_results.join("\n") + '
');
  },

  parseResult: function(result, token, field) {
    var subbed_result = this.subResult(result, token, this.search_options.fields[field])
    return this.wrapField(subbed_result, field);
  },

  wrapFields: function(results, object) {
    return ('
' + results + '
');
  },

  wrapField: function(result, field) {
    if (!result.blank()) {
      return ('' + result + '');
    }
  },

  classForFieldWrapper: function(field) {
    var klass = '';
    if (this.format_options.select && (this.format_options.select != field)) {
      klass = 'informal';
    }
    return klass;
  },

  subResult: function(result, token, pattern) {
    return result.sub(this.getRegex(token, pattern), this.getMatchFunction(pattern, result).bind(this));
  },

  getMatchFunction: function(pattern, result) {
    return this['matchFunctionFor' + pattern.split('_').invoke('capitalize').join('')](result);
  },

  matchFunctionForInfix: function(result) {
    return function(match) {
      if (match[3] != result) {
        return (match[1] + '' + match[2] + '' + match[3]);
      }
    }.bind(this);
  },

  matchFunctionForPrefix: function(result) {
    return function(match) {
      if (match[2] != result) {
        return ('' + match[1] + '' + match[2]);
      }
    }.bind(this);
  },

  matchFunctionForExact: function(result) {
    return function(match) {
      if (match != result) {
        return ('' + match[1] + '');
      }
    }.bind(this);
  },

  matchFunctionForWord: function(result) {
    return this.matchFunctionForInfix(result);
  },

  matchFunctionForWordPrefix: function(result) {
    return this.matchFunctionForInfix(result);
  },

  getRegex: function(token, pattern) {
    return new RegExp(this.patterns[pattern].gsub(/\$token/, RegExp.escape(token)), this.json_search.case_sensitive);
  },

  onClick: function($super, event) {
    $super(event);
    event.stop();
  }

});<commit_msg>Fixing getRegex to use json_search getRegexOptions api.<commit_after>Autocompleter.JSONSearch = Class.create(Autocompleter.Local, {

  patterns: {
    infix: '(.*?)($token)(.*?)',
    prefix: '^($token)(.*?)',
    exact: '^($token)$',
    word: '(.*?\\b)($token)(\\b.*?)',
    word_prefix: "^(.*?\\b)($token)(.*?)$"
  },

  initialize: function($super, element, update, data, options) {
    this.options = (options || {});
    this.format_options = this.extractFormatOptions();
    this.search_options = this.extractSearchOptions();
    this.setupJSONSearch();
    this.setupSearchSelector();
    $super(element, update, data, options);
    if (this.options.focus) {
      $(element).focus();
    }
  },

  setupJSONSearch: function() {
    this.json_search = new JSONSearch(this.search_options);
  },

  extractSearchOptions: function() {
    var search_options = Object.clone(this.options.search_options || {});
    if (Object.isUndefined(search_options.limit)) {
      search_options.limit = (this.options.choices || 10);
    }
    if (Object.isUndefined(search_options.case_sensitive)) {
      search_options.case_sensitive = !!this.options.ignoreCase;
    }
    return search_options;
  },

  extractFormatOptions: function() {
    var format_options = Object.clone(this.options.format_options);
    if (!format_options.order) {
      format_options.order = Object.keys(this.search_options.fields);
    }
    return format_options;
  },

  setupSearchSelector: function() {
    this.options.selector = function(instance) {
      var token = instance.getToken();
      var results = instance.json_search.getResults(token, instance.options.array);
      return instance.parseResults(results, token);
    }
  },

  parseResults: function(results, token) {
    var formatted_fields = [], formatted_results = [], self = this;
    results._each(function(result) {
      self.format_options.order._each(function(field) {
        formatted_fields.push(self.parseResult(result[field], token, field));
      });
      formatted_results.push(self.wrapFields(formatted_fields.compact().join("\n"), result));
    });
    return ('
' + formatted_results.join("\n") + '
');
  },

  parseResult: function(result, token, field) {
    var subbed_result = this.subResult(result, token, this.search_options.fields[field])
    return this.wrapField(subbed_result, field);
  },

  wrapFields: function(results, object) {
    return ('
' + results + '
');
  },

  wrapField: function(result, field) {
    if (!result.blank()) {
      return ('' + result + '');
    }
  },

  classForFieldWrapper: function(field) {
    var klass = '';
    if (this.format_options.select && (this.format_options.select != field)) {
      klass = 'informal';
    }
    return klass;
  },

  subResult: function(result, token, pattern) {
    return result.sub(this.getRegex(token, pattern), this.getMatchFunction(pattern, result).bind(this));
  },

  getMatchFunction: function(pattern, result) {
    return this['matchFunctionFor' + pattern.split('_').invoke('capitalize').join('')](result);
  },

  matchFunctionForInfix: function(result) {
    return function(match) {
      if (match[3] != result) {
        return (match[1] + '' + match[2] + '' + match[3]);
      }
    }.bind(this);
  },

  matchFunctionForPrefix: function(result) {
    return function(match) {
      if (match[2] != result) {
        return ('' + match[1] + '' + match[2]);
      }
    }.bind(this);
  },

  matchFunctionForExact: function(result) {
    return function(match) {
      if (match != result) {
        return ('' + match[1] + '');
      }
    }.bind(this);
  },

  matchFunctionForWord: function(result) {
    return this.matchFunctionForInfix(result);
  },

  matchFunctionForWordPrefix: function(result) {
    return this.matchFunctionForInfix(result);
  },

  getRegex: function(token, pattern) {
    return new RegExp(this.patterns[pattern].gsub(/\$token/, RegExp.escape(token)), this.json_search.getRegexOptions());
  },

  onClick: function($super, event) {
    $super(event);
    event.stop();
  }

});<|endoftext|>


<commit_before>/**
 *
 * to simulate native event use:
 *  var event = new CustomEvent(<event>, { detail: <data> } );
 *  document.dispatchEvent( event )
 *
 */

var browser = require('cordova/platform');

var _handler = function( ev ) {
  window.broadcaster.fireEvent( ev.type, ev.detail );
}

module.exports = {

  /**
   * opts: [eventname:string, data:any]
   */
   fireNativeEvent:function(success, error, opts) {
    var event = new CustomEvent(opts[0], { detail: opts[1] } );
    if( document.dispatchEvent( event ) ) success({});
  },
  /**
   * opts: [eventname:string]
   *
   */
  addEventListener:function (success, error, opts ) {
    document.addEventListener( opts[0], _handler )
    success({});
  },
  /**
   * opts: [eventname]
   */
  removeEventListener:function(success, error, opts) {
    document.removeventListener( opts[0], _handler )
    success({});
  }

}

require("cordova/exec/proxy").add("broadcaster", module.exports);
<commit_msg>fix  document.removeEventListener method mismatch<commit_after>/**
 *
 * to simulate native event use:
 *  var event = new CustomEvent(<event>, { detail: <data> } );
 *  document.dispatchEvent( event )
 *
 */

var browser = require('cordova/platform');

var _handler = function( ev ) {
  window.broadcaster.fireEvent( ev.type, ev.detail );
}

module.exports = {

  /**
   * opts: [eventname:string, data:any]
   */
   fireNativeEvent:function(success, error, opts) {
    var event = new CustomEvent(opts[0], { detail: opts[1] } );
    if( document.dispatchEvent( event ) ) success({});
  },
  /**
   * opts: [eventname:string]
   *
   */
  addEventListener:function (success, error, opts ) {
    document.addEventListener( opts[0], _handler )
    success({});
  },
  /**
   * opts: [eventname]
   */
  removeEventListener:function(success, error, opts) {
    document.removeEventListener( opts[0], _handler )
    success({});
  }

}

require("cordova/exec/proxy").add("broadcaster", module.exports);
<|endoftext|>


<commit_before>/**
 * @fileOverview The controller of tooltip
 * @author sima.zhang
 */
const Util = require('../../util');
const Global = require('../../global');
const { Tooltip } = require('../../component/index');
const MatrixUtil = require('@antv/g').MatrixUtil;
const Vector2 = MatrixUtil.vec2;

const TYPE_SHOW_MARKERS = [ 'line', 'area', 'path', 'areaStack' ]; // 默认展示 tooltip marker 的几何图形
const TYPE_SHOW_CROSSHAIRS = [ 'line', 'area' ]; // 默认展示十字瞄准线的几何图形

function _indexOfArray(items, item) {
  let rst = -1;
  Util.each(items, function(sub, index) {
    if (sub.title === item.title && sub.name === item.name && sub.value === item.value && sub.color === item.color) {
      rst = index;
      return false;
    }
  });
  return rst;
}

// 判断是否有样式
function _hasClass(dom, className) {
  if (!dom) {
    return false;
  }
  let cls = '';
  if (!dom.className) return false;
  if (!Util.isNil(dom.className.baseVal)) {
    cls = dom.className.baseVal;
  } else {
    cls = dom.className;
  }
  return cls.indexOf(className) !== -1;
}

function _isParent(dom, cls) {
  let parent = dom.parentNode;
  let rst = false;
  while (parent && parent !== document.body) {
    if (_hasClass(parent, cls)) {
      rst = true;
      break;
    }
    parent = parent.parentNode;
  }
  return rst;
}

// 去除重复的值, 去除不同图形相同数据，只展示一份即可
function _uniqItems(items) {
  const tmp = [];
  Util.each(items, function(item) {
    const index = _indexOfArray(tmp, item);
    if (index === -1) {
      tmp.push(item);
    } else {
      tmp[index] = item;
    }
  });
  return tmp;
}

class TooltipController {
  constructor(cfg) {
    Util.assign(this, cfg);
    this.timeStamp = 0;
  }

  _normalizeEvent(event) {
    const chart = this.chart;
    const canvas = this._getCanvas();
    const point = canvas.getPointByClient(event.clientX, event.clientY);
    const pixelRatio = canvas.get('pixelRatio');
    point.x = point.x / pixelRatio;
    point.y = point.y / pixelRatio;
    const views = chart.getViewsByPoint(point);
    point.views = views;
    return point;
  }

  _getCanvas() {
    return this.chart.get('canvas');
  }

  _getTriggerEvent() {
    const options = this.options;
    const triggerOn = options.triggerOn;
    let eventName;

    if (!triggerOn || triggerOn === 'mousemove') {
      eventName = 'plotmove';
    } else if (triggerOn === 'click') {
      eventName = 'plotclick';
    } else if (triggerOn === 'none') {
      eventName = null;
    }

    return eventName;
  }

  _getDefaultTooltipCfg() {
    const self = this;
    const options = self.options;
    const defaultCfg = Util.mix({}, Global.tooltip);
    const chart = self.chart;
    const geoms = chart.getAllGeoms().filter(function(geom) {
      return geom.get('visible');
    });
    const shapes = [];
    Util.each(geoms, function(geom) {
      const type = geom.get('type');
      const adjusts = geom.get('adjusts');
      let isSymmetric = false;
      if (adjusts) {
        Util.each(adjusts, adjust => {
          if (adjust.type === 'symmetric' || adjust.type === 'Symmetric') {
            isSymmetric = true;
            return false;
          }
        });
      }
      if (Util.indexOf(shapes, type) === -1 && !isSymmetric) {
        shapes.push(type);
      }
    });

    let crosshairsCfg;
    if (geoms.length && geoms[0].get('coord') && geoms[0].get('coord').type === 'cartesian' && shapes.length === 1) {
      if (shapes[0] === 'interval' && options.shared !== false) { // 直角坐标系下 interval 的 crosshair 为矩形背景框
        crosshairsCfg = {
          zIndex: 0, // 矩形背景框不可覆盖 geom
          crosshairs: Global.tooltipCrosshairsRect
        };
      } else if (Util.indexOf(TYPE_SHOW_CROSSHAIRS, shapes[0]) > -1) {
        crosshairsCfg = {
          crosshairs: Global.tooltipCrosshairsLine
        };
      }
    }

    return Util.mix(defaultCfg, crosshairsCfg, {
      isTransposed: geoms[0].get('coord').isTransposed
    });
  }

  _bindEvent() {
    const chart = this.chart;
    const triggerEvent = this._getTriggerEvent();
    if (triggerEvent) {
      chart.on(triggerEvent, Util.wrapBehavior(this, 'onMouseMove'));
      chart.on('plotleave', Util.wrapBehavior(this, 'onMouseOut'));
    }
  }

  _offEvent() {
    const chart = this.chart;
    const triggerEvent = this._getTriggerEvent();
    if (triggerEvent) {
      chart.off(triggerEvent, Util.getWrapBehavior(this, 'onMouseMove'));
      chart.off('plotleave', Util.getWrapBehavior(this, 'onMouseOut'));
    }
  }

  _setTooltip(point, items, markersItems, target) {
    const self = this;
    const tooltip = self.tooltip;
    const prePoint = self.prePoint;
    if (!prePoint || (prePoint.x !== point.x || prePoint.y !== point.y)) {
      items = _uniqItems(items);
      self.prePoint = point;

      const chart = self.chart;
      const x = Util.isArray(point.x) ? point.x[point.x.length - 1] : point.x;
      const y = Util.isArray(point.y) ? point.y[point.y.length - 1] : point.y;
      if (!tooltip.get('visible')) {
        chart.emit('tooltip:show', {
          x,
          y,
          tooltip
        });
      }
      chart.emit('tooltip:change', {
        tooltip,
        x,
        y,
        items
      });
      // bugfix: when set the title in the tooltip:change event does not take effect.
      const first = items[0];
      const title = first.title || first.name;
      tooltip.setContent(title, items);
      if (!Util.isEmpty(markersItems)) {
        if (self.options.hideMarkers === true) { // 不展示 tooltip marker
          tooltip.set('markerItems', markersItems); // 用于 tooltip 辅助线的定位
        } else {
          tooltip.setMarkers(markersItems, Global.tooltipMarker);
        }
      } else {
        tooltip.clearMarkers();
      }

      tooltip.setPosition(x, y, target);
      tooltip.show();
    }
  }

  hideTooltip() {
    const tooltip = this.tooltip;
    const chart = this.chart;
    const canvas = this._getCanvas();
    this.prePoint = null;
    tooltip.hide();
    chart.emit('tooltip:hide', {
      tooltip
    });
    canvas.draw();
  }

  onMouseMove(ev) {
    if (Util.isEmpty(ev.views)) {
      return;
    }

    const lastTimeStamp = this.timeStamp;
    const timeStamp = +new Date();
    const point = {
      x: ev.x,
      y: ev.y
    };
    if ((timeStamp - lastTimeStamp) > 16) {
      let target;
      if (ev.shape
        && Util.inArray([ 'point', 'interval', 'polygon', 'schema' ], ev.shape.name)) {
        target = ev.shape;
      }
      this.showTooltip(point, ev.views, target);
      this.timeStamp = timeStamp;
    }
  }

  onMouseOut(ev) {
    const tooltip = this.tooltip;
    const canvas = this._getCanvas();
    if (!tooltip.get('visible')) {
      return;
    }
    if (ev && ev.target !== canvas) {
      return;
    }
    if (ev && ev.toElement && (_hasClass(ev.toElement, 'g2-tooltip') || _isParent(ev.toElement, 'g2-tooltip'))) {
      return;
    }
    this.hideTooltip();
  }

  renderTooltip() {
    const self = this;
    if (self.tooltip) { // tooltip 对象已经创建
      return;
    }
    const chart = self.chart;
    const canvas = self._getCanvas();
    const defaultCfg = self._getDefaultTooltipCfg();
    let options = self.options;
    options = Util.deepMix({
      plotRange: chart.get('plotRange'),
      capture: false,
      canvas,
      frontPlot: chart.get('frontPlot'),
      backPlot: chart.get('backPlot')
    }, defaultCfg, options);
    if (options.crosshairs && options.crosshairs.type === 'rect') {
      options.zIndex = 0; // toolip 背景框不可遮盖住 geom，防止用户配置了 crosshairs
    }

    options.visible = false;
    if (options.shared === false && Util.isNil(options.position)) {
      options.position = 'top';
    }

    const tooltip = new Tooltip(options);
    self.tooltip = tooltip;

    const triggerEvent = self._getTriggerEvent();
    if (!tooltip.get('enterable') && triggerEvent === 'plotmove') { // 鼠标不允许进入 tooltip 容器
      const tooltipContainer = tooltip.get('container');
      if (tooltipContainer) {
        tooltipContainer.onmousemove = e => {
          // 避免 tooltip 频繁闪烁
          const eventObj = self._normalizeEvent(e);
          chart.emit(triggerEvent, eventObj);
        };
      }
    }
    self._bindEvent();
  }

  showTooltip(point, views, target) {
    const self = this;
    if (Util.isEmpty(views) || !point) {
      return;
    }
    if (!this.tooltip) {
      this.renderTooltip(); // 如果一开始 tooltip 关闭，用户重新调用的时候需要先生成 tooltip
    }
    const options = self.options;
    let markersItems = [];
    let items = [];

    Util.each(views, view => {
      if (!view.get('tooltipEnable')) { // 如果不显示tooltip，则跳过
        return true;
      }
      const geoms = view.get('geoms');
      const coord = view.get('coord');
      Util.each(geoms, geom => {
        const type = geom.get('type');
        if (geom.get('visible') && geom.get('tooltipCfg') !== false) {
          const dataArray = geom.get('dataArray');
          if (geom.isShareTooltip() || (options.shared === false && Util.inArray([ 'area', 'line', 'path' ], type))) {
            Util.each(dataArray, function(obj) {
              const tmpPoint = geom.findPoint(point, obj);
              if (tmpPoint) {
                const subItems = geom.getTipItems(tmpPoint, options.title);
                if (Util.indexOf(TYPE_SHOW_MARKERS, type) !== -1) {
                  Util.each(subItems, v => {
                    let point = v.point;
                    if (point && point.x && point.y) { // hotfix: make sure there is no null value
                      const x = Util.isArray(point.x) ? point.x[point.x.length - 1] : point.x;
                      const y = Util.isArray(point.y) ? point.y[point.y.length - 1] : point.y;
                      point = coord.applyMatrix(x, y, 1);
                      v.x = point[0];
                      v.y = point[1];
                      v.showMarker = true;
                      markersItems.push(v);
                    }
                  });
                }
                items = items.concat(subItems);
              }
            });
          } else {
            const geomContainer = geom.get('shapeContainer');
            const canvas = geomContainer.get('canvas');
            const pixelRatio = canvas.get('pixelRatio');
            const shape = geomContainer.getShape(point.x * pixelRatio, point.y * pixelRatio);
            if (shape && shape.get('visible') && shape.get('origin')) {
              items = geom.getTipItems(shape.get('origin'), options.title);
            }
          }
        }
      });

      Util.each(items, item => {
        let point = item.point;
        const x = Util.isArray(point.x) ? point.x[point.x.length - 1] : point.x;
        const y = Util.isArray(point.y) ? point.y[point.y.length - 1] : point.y;
        point = coord.applyMatrix(x, y, 1);
        item.x = point[0];
        item.y = point[1];
      });
    });

    if (items.length) {
      const first = items[0];

      // bugfix: multiple tooltip items with different titles
      if (!items.every(item => item.title === first.title)) {
        let nearestItem = first;
        let nearestDistance = Infinity;
        items.forEach(item => {
          const distance = Vector2.distance([ point.x, point.y ], [ item.x, item.y ]);
          if (distance < nearestDistance) {
            nearestDistance = distance;
            nearestItem = item;
          }
        });
        items = items.filter(item => item.title === nearestItem.title);
        markersItems = markersItems.filter(item => item.title === nearestItem.title);
      }

      if (options.shared === false && items.length > 1) {
        let snapItem = items[0];
        let min = Math.abs(point.y - snapItem.y);
        Util.each(items, aItem => {
          if (Math.abs(point.y - aItem.y) <= min) {
            snapItem = aItem;
            min = Math.abs(point.y - aItem.y);
          }
        });
        if (snapItem && snapItem.x && snapItem.y) {
          markersItems = [ snapItem ];
        }
        items = [ snapItem ];
      }
      // 3.0 采用当前鼠标位置作为 tooltip 的参考点
      // if (!Util.isEmpty(markersItems)) {
      //   point = markersItems[0];
      // }
      self._setTooltip(point, items, markersItems, target);
    } else {
      self.hideTooltip();
    }
  }

  clear() {
    const tooltip = this.tooltip;
    tooltip && tooltip.destroy();
    this.tooltip = null;
    this.prePoint = null;
    this._offEvent();
  }
}

module.exports = TooltipController;

<commit_msg>fix(tooltip): rendering without geoms<commit_after>/**
 * @fileOverview The controller of tooltip
 * @author sima.zhang
 */
const Util = require('../../util');
const Global = require('../../global');
const { Tooltip } = require('../../component/index');
const MatrixUtil = require('@antv/g').MatrixUtil;
const Vector2 = MatrixUtil.vec2;

const TYPE_SHOW_MARKERS = [ 'line', 'area', 'path', 'areaStack' ]; // 默认展示 tooltip marker 的几何图形
const TYPE_SHOW_CROSSHAIRS = [ 'line', 'area' ]; // 默认展示十字瞄准线的几何图形

function _indexOfArray(items, item) {
  let rst = -1;
  Util.each(items, function(sub, index) {
    if (sub.title === item.title && sub.name === item.name && sub.value === item.value && sub.color === item.color) {
      rst = index;
      return false;
    }
  });
  return rst;
}

// 判断是否有样式
function _hasClass(dom, className) {
  if (!dom) {
    return false;
  }
  let cls = '';
  if (!dom.className) return false;
  if (!Util.isNil(dom.className.baseVal)) {
    cls = dom.className.baseVal;
  } else {
    cls = dom.className;
  }
  return cls.indexOf(className) !== -1;
}

function _isParent(dom, cls) {
  let parent = dom.parentNode;
  let rst = false;
  while (parent && parent !== document.body) {
    if (_hasClass(parent, cls)) {
      rst = true;
      break;
    }
    parent = parent.parentNode;
  }
  return rst;
}

// 去除重复的值, 去除不同图形相同数据，只展示一份即可
function _uniqItems(items) {
  const tmp = [];
  Util.each(items, function(item) {
    const index = _indexOfArray(tmp, item);
    if (index === -1) {
      tmp.push(item);
    } else {
      tmp[index] = item;
    }
  });
  return tmp;
}

class TooltipController {
  constructor(cfg) {
    Util.assign(this, cfg);
    this.timeStamp = 0;
  }

  _normalizeEvent(event) {
    const chart = this.chart;
    const canvas = this._getCanvas();
    const point = canvas.getPointByClient(event.clientX, event.clientY);
    const pixelRatio = canvas.get('pixelRatio');
    point.x = point.x / pixelRatio;
    point.y = point.y / pixelRatio;
    const views = chart.getViewsByPoint(point);
    point.views = views;
    return point;
  }

  _getCanvas() {
    return this.chart.get('canvas');
  }

  _getTriggerEvent() {
    const options = this.options;
    const triggerOn = options.triggerOn;
    let eventName;

    if (!triggerOn || triggerOn === 'mousemove') {
      eventName = 'plotmove';
    } else if (triggerOn === 'click') {
      eventName = 'plotclick';
    } else if (triggerOn === 'none') {
      eventName = null;
    }

    return eventName;
  }

  _getDefaultTooltipCfg() {
    const self = this;
    const options = self.options;
    const defaultCfg = Util.mix({}, Global.tooltip);
    const chart = self.chart;
    const geoms = chart.getAllGeoms().filter(function(geom) {
      return geom.get('visible');
    });
    const shapes = [];
    Util.each(geoms, function(geom) {
      const type = geom.get('type');
      const adjusts = geom.get('adjusts');
      let isSymmetric = false;
      if (adjusts) {
        Util.each(adjusts, adjust => {
          if (adjust.type === 'symmetric' || adjust.type === 'Symmetric') {
            isSymmetric = true;
            return false;
          }
        });
      }
      if (Util.indexOf(shapes, type) === -1 && !isSymmetric) {
        shapes.push(type);
      }
    });

    let crosshairsCfg;
    if (geoms.length && geoms[0].get('coord') && geoms[0].get('coord').type === 'cartesian' && shapes.length === 1) {
      if (shapes[0] === 'interval' && options.shared !== false) { // 直角坐标系下 interval 的 crosshair 为矩形背景框
        crosshairsCfg = {
          zIndex: 0, // 矩形背景框不可覆盖 geom
          crosshairs: Global.tooltipCrosshairsRect
        };
      } else if (Util.indexOf(TYPE_SHOW_CROSSHAIRS, shapes[0]) > -1) {
        crosshairsCfg = {
          crosshairs: Global.tooltipCrosshairsLine
        };
      }
    }

    return Util.mix(defaultCfg, crosshairsCfg, {
      isTransposed: geoms.length && geoms[0].get('coord') ? geoms[0].get('coord').isTransposed : false
    });
  }

  _bindEvent() {
    const chart = this.chart;
    const triggerEvent = this._getTriggerEvent();
    if (triggerEvent) {
      chart.on(triggerEvent, Util.wrapBehavior(this, 'onMouseMove'));
      chart.on('plotleave', Util.wrapBehavior(this, 'onMouseOut'));
    }
  }

  _offEvent() {
    const chart = this.chart;
    const triggerEvent = this._getTriggerEvent();
    if (triggerEvent) {
      chart.off(triggerEvent, Util.getWrapBehavior(this, 'onMouseMove'));
      chart.off('plotleave', Util.getWrapBehavior(this, 'onMouseOut'));
    }
  }

  _setTooltip(point, items, markersItems, target) {
    const self = this;
    const tooltip = self.tooltip;
    const prePoint = self.prePoint;
    if (!prePoint || (prePoint.x !== point.x || prePoint.y !== point.y)) {
      items = _uniqItems(items);
      self.prePoint = point;

      const chart = self.chart;
      const x = Util.isArray(point.x) ? point.x[point.x.length - 1] : point.x;
      const y = Util.isArray(point.y) ? point.y[point.y.length - 1] : point.y;
      if (!tooltip.get('visible')) {
        chart.emit('tooltip:show', {
          x,
          y,
          tooltip
        });
      }
      chart.emit('tooltip:change', {
        tooltip,
        x,
        y,
        items
      });
      // bugfix: when set the title in the tooltip:change event does not take effect.
      const first = items[0];
      const title = first.title || first.name;
      tooltip.setContent(title, items);
      if (!Util.isEmpty(markersItems)) {
        if (self.options.hideMarkers === true) { // 不展示 tooltip marker
          tooltip.set('markerItems', markersItems); // 用于 tooltip 辅助线的定位
        } else {
          tooltip.setMarkers(markersItems, Global.tooltipMarker);
        }
      } else {
        tooltip.clearMarkers();
      }

      tooltip.setPosition(x, y, target);
      tooltip.show();
    }
  }

  hideTooltip() {
    const tooltip = this.tooltip;
    const chart = this.chart;
    const canvas = this._getCanvas();
    this.prePoint = null;
    tooltip.hide();
    chart.emit('tooltip:hide', {
      tooltip
    });
    canvas.draw();
  }

  onMouseMove(ev) {
    if (Util.isEmpty(ev.views)) {
      return;
    }

    const lastTimeStamp = this.timeStamp;
    const timeStamp = +new Date();
    const point = {
      x: ev.x,
      y: ev.y
    };
    if ((timeStamp - lastTimeStamp) > 16) {
      let target;
      if (ev.shape
        && Util.inArray([ 'point', 'interval', 'polygon', 'schema' ], ev.shape.name)) {
        target = ev.shape;
      }
      this.showTooltip(point, ev.views, target);
      this.timeStamp = timeStamp;
    }
  }

  onMouseOut(ev) {
    const tooltip = this.tooltip;
    const canvas = this._getCanvas();
    if (!tooltip.get('visible')) {
      return;
    }
    if (ev && ev.target !== canvas) {
      return;
    }
    if (ev && ev.toElement && (_hasClass(ev.toElement, 'g2-tooltip') || _isParent(ev.toElement, 'g2-tooltip'))) {
      return;
    }
    this.hideTooltip();
  }

  renderTooltip() {
    const self = this;
    if (self.tooltip) { // tooltip 对象已经创建
      return;
    }
    const chart = self.chart;
    const canvas = self._getCanvas();
    const defaultCfg = self._getDefaultTooltipCfg();
    let options = self.options;
    options = Util.deepMix({
      plotRange: chart.get('plotRange'),
      capture: false,
      canvas,
      frontPlot: chart.get('frontPlot'),
      backPlot: chart.get('backPlot')
    }, defaultCfg, options);
    if (options.crosshairs && options.crosshairs.type === 'rect') {
      options.zIndex = 0; // toolip 背景框不可遮盖住 geom，防止用户配置了 crosshairs
    }

    options.visible = false;
    if (options.shared === false && Util.isNil(options.position)) {
      options.position = 'top';
    }

    const tooltip = new Tooltip(options);
    self.tooltip = tooltip;

    const triggerEvent = self._getTriggerEvent();
    if (!tooltip.get('enterable') && triggerEvent === 'plotmove') { // 鼠标不允许进入 tooltip 容器
      const tooltipContainer = tooltip.get('container');
      if (tooltipContainer) {
        tooltipContainer.onmousemove = e => {
          // 避免 tooltip 频繁闪烁
          const eventObj = self._normalizeEvent(e);
          chart.emit(triggerEvent, eventObj);
        };
      }
    }
    self._bindEvent();
  }

  showTooltip(point, views, target) {
    const self = this;
    if (Util.isEmpty(views) || !point) {
      return;
    }
    if (!this.tooltip) {
      this.renderTooltip(); // 如果一开始 tooltip 关闭，用户重新调用的时候需要先生成 tooltip
    }
    const options = self.options;
    let markersItems = [];
    let items = [];

    Util.each(views, view => {
      if (!view.get('tooltipEnable')) { // 如果不显示tooltip，则跳过
        return true;
      }
      const geoms = view.get('geoms');
      const coord = view.get('coord');
      Util.each(geoms, geom => {
        const type = geom.get('type');
        if (geom.get('visible') && geom.get('tooltipCfg') !== false) {
          const dataArray = geom.get('dataArray');
          if (geom.isShareTooltip() || (options.shared === false && Util.inArray([ 'area', 'line', 'path' ], type))) {
            Util.each(dataArray, function(obj) {
              const tmpPoint = geom.findPoint(point, obj);
              if (tmpPoint) {
                const subItems = geom.getTipItems(tmpPoint, options.title);
                if (Util.indexOf(TYPE_SHOW_MARKERS, type) !== -1) {
                  Util.each(subItems, v => {
                    let point = v.point;
                    if (point && point.x && point.y) { // hotfix: make sure there is no null value
                      const x = Util.isArray(point.x) ? point.x[point.x.length - 1] : point.x;
                      const y = Util.isArray(point.y) ? point.y[point.y.length - 1] : point.y;
                      point = coord.applyMatrix(x, y, 1);
                      v.x = point[0];
                      v.y = point[1];
                      v.showMarker = true;
                      markersItems.push(v);
                    }
                  });
                }
                items = items.concat(subItems);
              }
            });
          } else {
            const geomContainer = geom.get('shapeContainer');
            const canvas = geomContainer.get('canvas');
            const pixelRatio = canvas.get('pixelRatio');
            const shape = geomContainer.getShape(point.x * pixelRatio, point.y * pixelRatio);
            if (shape && shape.get('visible') && shape.get('origin')) {
              items = geom.getTipItems(shape.get('origin'), options.title);
            }
          }
        }
      });

      Util.each(items, item => {
        let point = item.point;
        const x = Util.isArray(point.x) ? point.x[point.x.length - 1] : point.x;
        const y = Util.isArray(point.y) ? point.y[point.y.length - 1] : point.y;
        point = coord.applyMatrix(x, y, 1);
        item.x = point[0];
        item.y = point[1];
      });
    });

    if (items.length) {
      const first = items[0];

      // bugfix: multiple tooltip items with different titles
      if (!items.every(item => item.title === first.title)) {
        let nearestItem = first;
        let nearestDistance = Infinity;
        items.forEach(item => {
          const distance = Vector2.distance([ point.x, point.y ], [ item.x, item.y ]);
          if (distance < nearestDistance) {
            nearestDistance = distance;
            nearestItem = item;
          }
        });
        items = items.filter(item => item.title === nearestItem.title);
        markersItems = markersItems.filter(item => item.title === nearestItem.title);
      }

      if (options.shared === false && items.length > 1) {
        let snapItem = items[0];
        let min = Math.abs(point.y - snapItem.y);
        Util.each(items, aItem => {
          if (Math.abs(point.y - aItem.y) <= min) {
            snapItem = aItem;
            min = Math.abs(point.y - aItem.y);
          }
        });
        if (snapItem && snapItem.x && snapItem.y) {
          markersItems = [ snapItem ];
        }
        items = [ snapItem ];
      }
      // 3.0 采用当前鼠标位置作为 tooltip 的参考点
      // if (!Util.isEmpty(markersItems)) {
      //   point = markersItems[0];
      // }
      self._setTooltip(point, items, markersItems, target);
    } else {
      self.hideTooltip();
    }
  }

  clear() {
    const tooltip = this.tooltip;
    tooltip && tooltip.destroy();
    this.tooltip = null;
    this.prePoint = null;
    this._offEvent();
  }
}

module.exports = TooltipController;

<|endoftext|>


<commit_before>var clusteredforce = function (userConfig) {
  d3 = dex.charts.d3.d3v3;
  var defaults =
  {
    'parent': null,
    'id': "ClusteredForce",
    'class': "ClusteredForce",
    'height': "100%",
    'width': "100%",
    'csv': {
      'header': ["X", "Y"],
      'data': [
        [0, 0],
        [1, 1],
        [2, 4],
        [3, 9],
        [4, 16]
      ]
    },
    'groups': [{'category': 0, 'value': 1, 'label': 0}],
    'transform': '',
    'color': d3.scale.category20(),
    'padding': 10,
    // TODO: Add normalization function.
    'sizingFunction': function () {
      return d3.scale.linear()
    },
    'minRadius': 5,
    'maxRadius': 20,
    'gravity': 2,
    'charge': 0,
    'scaleColumns': true,
    'circle': dex.config.circle({
      'r': function (d) {
        return (dex.object.isNumeric(d.radius) ? d.radius : 1);
      },
      'fill': dex.config.fill({
        'fillColor': function (d, i) {
          var darkColor = dex.color.shadeColor(d.color, -10);
          var gradientId = "gradient" + d.color.substring(1)
          var grad = d3.select(chart.config.parent)
            .select("#gradients")
            .selectAll("#" + gradientId)
            .data([gradientId])
            .enter()
            .append("radialGradient")
            .attr("class", "colorGradient")
            .attr("id", gradientId)
            .attr("gradientUnits", "objectBoundingBox")
            .attr("fx", "30%")
            .attr("fy", "30%");

          grad.append("stop")
            .attr("offset", "0%")
            .attr("style", "stop-color:#FFFFFF");

          // Middle
          grad.append("stop")
            .attr("offset", "90%")
            .attr("style", "stop-color:" + d.color);

          // Outer Edges
          grad.append("stop")
            .attr("offset", "100%")
            .attr("style", "stop-color:" + darkColor);

          return "url(#" + gradientId + ")";
        }
      }),
      'stroke': dex.config.stroke(),
      'tooltip': function (d) {
        return d.text;
      },
      'transform': ''
    })
  };

  var chart = new dex.component(userConfig, defaults);

  chart.render = function () {
    d3 = dex.charts.d3.d3v3;
    window.onresize = this.resize;
    chart.resize();
  };

  chart.resize = function () {
    d3 = dex.charts.d3.d3v3;
    d3.selectAll("#" + chart.config.id).remove();
    var width = d3.select(chart.config.parent).property("clientWidth");
    var height = d3.select(chart.config.parent).property("clientHeight");
    chart.attr("width", width).attr("height", height).update();
  };

  chart.update = function () {
    d3 = dex.charts.d3.d3v3;
    var config = chart.config;

    var csv = config.csv;

    var radius = d3.scale.sqrt().range([0, 12]);

    /*
    var minValue, maxValue;

    if (!config.scaleColumns) {
      minValue = dex.matrix.min(csv.data, numericIndices[0]);
      maxValue = dex.matrix.max(csv.data, numericIndices[0]);
      for (i = 0; i < numericIndices.length; i++) {
        minValue = Math.min(minValue, dex.matrix.min(csv.data, numericIndices[i]));
        maxValue = Math.max(maxValue, dex.matric.max(csv.data, numericIndices[i]));
      }
    }
*/

    var nodes = [];

    var values = [];
    var min = null;
    var max = null;

    config.groups.forEach(function (group) {
      "use strict";
      config.csv.data.forEach(function (row) {
        var value = +(row[group.value]);
        nodes.push({
          'category': row[group.category],
          'value': +value,
          'color' : config.color(row[group.category]),
          'text': "

Label		" + row[group.label] +
          "

Category	" + row[group.category] + "
" +
          "
Value	" + row[group.value] +
          "

"
        });
        if (min == null || min > +value) {
          min = +value;
        }

        if (max == null || max < +value) {
          max = +value;
        }
      })
    });

    var radiusScale = d3.scale.linear()
      .domain([min, max])
      .range([config.minRadius, config.maxRadius]);

    nodes.forEach(function (node) {
      "use strict";
      node.radius = radiusScale(+node.value);
    });

    dex.console.log("NODES", nodes, "VALUES", values, "EXTENTS", min, max);

    force = d3.layout.force()
      .nodes(nodes)
      .size([config.width, config.height])
      .gravity(config.gravity / 100.0)
      .charge(config.charge / 100.0)
      .on("tick", tick)
      .start();

    var chartContainer = d3.select(config.parent);

    chartContainer.append('defs')
      .attr('id', 'gradients');

    chartContainer.append("g")
      .attr("id", config["id"])
      .attr("class", config["class"])
      .attr("transform", config.transform);

    var circle = chartContainer.selectAll("circle")
      .data(nodes)
      .enter().append("circle")
      .call(dex.config.configureCircle, config.circle)
      .call(force.drag);

    circle.append("text")
      .text(config.circle.tooltip);

    function tick(e) {
      circle
        .each(cluster(10 * e.alpha * e.alpha))
        .each(collide(.5))
        .attr("radius", function (d) {
          return (dex.object.isNumeric(d.radius) ? d.radius : 1);
        })
        .attr("cx", function (d) {
          return (dex.object.isNumeric(d.x) ? d.x : 0);
        })
        .attr("cy", function (d) {
          return (dex.object.isNumeric(d.y) ? d.y : 0);
        });
    }

    // Move d to be adjacent to the cluster node.
    function cluster(alpha) {
      var max = {};

      // Find the largest node for each cluster.
      nodes.forEach(function (d) {
        if (!(d.color in max) || (d.radius > max[d.color].radius)) {
          max[d.color] = d;
        }
      });

      return function (d) {
        var node = max[d.color],
          l,
          r,
          x,
          y,
          i = -1;

        if (node == d) return;

        x = d.x - node.x;
        y = d.y - node.y;
        l = Math.sqrt(x * x + y * y);
        r = d.radius + node.radius;
        if (l != r) {
          l = (l - r) / l * alpha;
          d.x -= x *= l;
          d.y -= y *= l;
          node.x += x;
          node.y += y;
        }
      };
    }

    // Resolves collisions between d and all other circles.
    function collide(alpha) {
      var quadtree = d3.geom.quadtree(nodes);
      return function (d) {
        var r = d.radius + radius.domain()[1] + config.padding,
          nx1 = d.x - r,
          nx2 = d.x + r,
          ny1 = d.y - r,
          ny2 = d.y + r;
        quadtree.visit(function (quad, x1, y1, x2, y2) {
          if (quad.point && (quad.point !== d)) {
            var x = d.x - quad.point.x,
              y = d.y - quad.point.y,
              l = Math.sqrt(x * x + y * y),
              r = d.radius + quad.point.radius + (d.color !== quad.point.color) * config.padding;
            if (l < r) {
              l = (l - r) / l * alpha;
              d.x -= x *= l;
              d.y -= y *= l;
              quad.point.x += x;
              quad.point.y += y;
            }
          }
          return x1 > nx2
            || x2 < nx1
            || y1 > ny2
            || y2 < ny1;
        });
      };
    }
  };

  $(document).ready(function () {
    $(chart.config.parent).uitooltip({
      items: "circle",
      position: {
        my: "right bottom+50"
      },
      content: function () {
        return $(this).find("text").text();
      },
      track: true
    });

    // Make the entire chart draggable.
    //$(chart.config.parent).draggable();
  });

  return chart;
};

module.exports = clusteredforce;<commit_msg>Set parent default<commit_after>var clusteredforce = function (userConfig) {
  d3 = dex.charts.d3.d3v3;
  var defaults =
  {
    'parent': '#ClusteredForceParent',
    'id': "ClusteredForce",
    'class': "ClusteredForce",
    'height': "100%",
    'width': "100%",
    'csv': {
      'header': ["X", "Y"],
      'data': [
        [0, 0],
        [1, 1],
        [2, 4],
        [3, 9],
        [4, 16]
      ]
    },
    'groups': [{'category': 0, 'value': 1, 'label': 0}],
    'transform': '',
    'color': d3.scale.category20(),
    'padding': 10,
    // TODO: Add normalization function.
    'sizingFunction': function () {
      return d3.scale.linear()
    },
    'minRadius': 5,
    'maxRadius': 20,
    'gravity': 2,
    'charge': 0,
    'scaleColumns': true,
    'circle': dex.config.circle({
      'r': function (d) {
        return (dex.object.isNumeric(d.radius) ? d.radius : 1);
      },
      'fill': dex.config.fill({
        'fillColor': function (d, i) {
          var darkColor = dex.color.shadeColor(d.color, -10);
          var gradientId = "gradient" + d.color.substring(1)
          var grad = d3.select(chart.config.parent)
            .select("#gradients")
            .selectAll("#" + gradientId)
            .data([gradientId])
            .enter()
            .append("radialGradient")
            .attr("class", "colorGradient")
            .attr("id", gradientId)
            .attr("gradientUnits", "objectBoundingBox")
            .attr("fx", "30%")
            .attr("fy", "30%");

          grad.append("stop")
            .attr("offset", "0%")
            .attr("style", "stop-color:#FFFFFF");

          // Middle
          grad.append("stop")
            .attr("offset", "90%")
            .attr("style", "stop-color:" + d.color);

          // Outer Edges
          grad.append("stop")
            .attr("offset", "100%")
            .attr("style", "stop-color:" + darkColor);

          return "url(#" + gradientId + ")";
        }
      }),
      'stroke': dex.config.stroke(),
      'tooltip': function (d) {
        return d.text;
      },
      'transform': ''
    })
  };

  var chart = new dex.component(userConfig, defaults);

  chart.render = function () {
    d3 = dex.charts.d3.d3v3;
    window.onresize = this.resize;
    chart.resize();
  };

  chart.resize = function () {
    d3 = dex.charts.d3.d3v3;
    d3.selectAll("#" + chart.config.id).remove();
    var width = d3.select(chart.config.parent).property("clientWidth");
    var height = d3.select(chart.config.parent).property("clientHeight");
    chart.attr("width", width).attr("height", height).update();
  };

  chart.update = function () {
    d3 = dex.charts.d3.d3v3;
    var config = chart.config;

    var csv = config.csv;

    var radius = d3.scale.sqrt().range([0, 12]);

    /*
    var minValue, maxValue;

    if (!config.scaleColumns) {
      minValue = dex.matrix.min(csv.data, numericIndices[0]);
      maxValue = dex.matrix.max(csv.data, numericIndices[0]);
      for (i = 0; i < numericIndices.length; i++) {
        minValue = Math.min(minValue, dex.matrix.min(csv.data, numericIndices[i]));
        maxValue = Math.max(maxValue, dex.matric.max(csv.data, numericIndices[i]));
      }
    }
*/

    var nodes = [];

    var values = [];
    var min = null;
    var max = null;

    config.groups.forEach(function (group) {
      "use strict";
      config.csv.data.forEach(function (row) {
        var value = +(row[group.value]);
        nodes.push({
          'category': row[group.category],
          'value': +value,
          'color' : config.color(row[group.category]),
          'text': "

Label		" + row[group.label] +
          "

Category	" + row[group.category] + "
" +
          "
Value	" + row[group.value] +
          "

"
        });
        if (min == null || min > +value) {
          min = +value;
        }

        if (max == null || max < +value) {
          max = +value;
        }
      })
    });

    var radiusScale = d3.scale.linear()
      .domain([min, max])
      .range([config.minRadius, config.maxRadius]);

    nodes.forEach(function (node) {
      "use strict";
      node.radius = radiusScale(+node.value);
    });

    dex.console.log("NODES", nodes, "VALUES", values, "EXTENTS", min, max);

    force = d3.layout.force()
      .nodes(nodes)
      .size([config.width, config.height])
      .gravity(config.gravity / 100.0)
      .charge(config.charge / 100.0)
      .on("tick", tick)
      .start();

    var chartContainer = d3.select(config.parent);

    chartContainer.append('defs')
      .attr('id', 'gradients');

    chartContainer.append("g")
      .attr("id", config["id"])
      .attr("class", config["class"])
      .attr("transform", config.transform);

    var circle = chartContainer.selectAll("circle")
      .data(nodes)
      .enter().append("circle")
      .call(dex.config.configureCircle, config.circle)
      .call(force.drag);

    circle.append("text")
      .text(config.circle.tooltip);

    function tick(e) {
      circle
        .each(cluster(10 * e.alpha * e.alpha))
        .each(collide(.5))
        .attr("radius", function (d) {
          return (dex.object.isNumeric(d.radius) ? d.radius : 1);
        })
        .attr("cx", function (d) {
          return (dex.object.isNumeric(d.x) ? d.x : 0);
        })
        .attr("cy", function (d) {
          return (dex.object.isNumeric(d.y) ? d.y : 0);
        });
    }

    // Move d to be adjacent to the cluster node.
    function cluster(alpha) {
      var max = {};

      // Find the largest node for each cluster.
      nodes.forEach(function (d) {
        if (!(d.color in max) || (d.radius > max[d.color].radius)) {
          max[d.color] = d;
        }
      });

      return function (d) {
        var node = max[d.color],
          l,
          r,
          x,
          y,
          i = -1;

        if (node == d) return;

        x = d.x - node.x;
        y = d.y - node.y;
        l = Math.sqrt(x * x + y * y);
        r = d.radius + node.radius;
        if (l != r) {
          l = (l - r) / l * alpha;
          d.x -= x *= l;
          d.y -= y *= l;
          node.x += x;
          node.y += y;
        }
      };
    }

    // Resolves collisions between d and all other circles.
    function collide(alpha) {
      var quadtree = d3.geom.quadtree(nodes);
      return function (d) {
        var r = d.radius + radius.domain()[1] + config.padding,
          nx1 = d.x - r,
          nx2 = d.x + r,
          ny1 = d.y - r,
          ny2 = d.y + r;
        quadtree.visit(function (quad, x1, y1, x2, y2) {
          if (quad.point && (quad.point !== d)) {
            var x = d.x - quad.point.x,
              y = d.y - quad.point.y,
              l = Math.sqrt(x * x + y * y),
              r = d.radius + quad.point.radius + (d.color !== quad.point.color) * config.padding;
            if (l < r) {
              l = (l - r) / l * alpha;
              d.x -= x *= l;
              d.y -= y *= l;
              quad.point.x += x;
              quad.point.y += y;
            }
          }
          return x1 > nx2
            || x2 < nx1
            || y1 > ny2
            || y2 < ny1;
        });
      };
    }
  };

  $(document).ready(function () {
    $(chart.config.parent).uitooltip({
      items: "circle",
      position: {
        my: "right bottom+50"
      },
      content: function () {
        return $(this).find("text").text();
      },
      track: true
    });

    // Make the entire chart draggable.
    //$(chart.config.parent).draggable();
  });

  return chart;
};

module.exports = clusteredforce;<|endoftext|>


<commit_before>// How to use:
// getValidation("Example,CSV,content\na,b,c\n")
//  .then(function(validation) {console.log(validation)})

function getValidation(content) {
  request = require('request');
  content = new Buffer(content).toString("base64");
  content = "editor.csv;data:text/csv;base64," + content;
  return new Promise(function(resolve, reject) {
    request.post("http://csvlint.io/package.json", { formData: {"files_data[]": content } }, function(error, response, body) {

      if (error) return reject(error);

      var packageURL = JSON.parse(response.body).package.url;
      var interval = setInterval(function() {
        request.get(packageURL + ".json", function(error, response, body) {
          try {
            var validationURL = JSON.parse(body).package.validations[0].url;
            clearInterval(interval);
            request.get(validationURL + ".json", function(error, response, body) {
              if (error) return reject(error);
              resolve(JSON.parse(body));
            });
          } catch(e) {}
        });
      }, 1000);

    });
  });
}

function validate() {
  data = hot.getData().map(function(d) { return d.join(",") }).join("\n")
  json_data = '{"version":"0.1","licence":"http://opendatacommons.org/licenses/odbl/","validation":{"url":"http://csvlint.io/validation/55ca092f63737668de000069","source":"http://theodi.github.io/hot-drinks/hot-drinks.csv","state":"warnings","errors":[],"warnings":[{"type":"malformed_header","category":"schema","row":1,"col":null}],"info":[{"type":"assumed_header","category":"structure","row":null,"col":null},{"type":"nonrfc_line_breaks","category":"structure","row":null,"col":null}],"standardisedCSV":"http://csvlint.io/validation/55ca092f63737668de000069.csv","badges":{"svg":"http://csvlint.io/validation/55ca092f63737668de000069.svg","png":"http://csvlint.io/validation/55ca092f63737668de000069.png"}}}'
  json_data_2 = getValidation("Interns,LOL,,,,a\nas,f").then(function(json_validation) {
    errors = json_validation.validation.errors
    warnings = json_validation.validation.warnings
    info_messages = json_validation.validation.info
    console.error(errors)
    console.warn(warnings)
    console.info(info_messages);
  });
}
<commit_msg>Got rid of some rough work<commit_after>// How to use:
// getValidation("Example,CSV,content\na,b,c\n")
//  .then(function(validation) {console.log(validation)})

function getValidation(content) {
  request = require('request');
  content = new Buffer(content).toString("base64");
  content = "editor.csv;data:text/csv;base64," + content;
  return new Promise(function(resolve, reject) {
    request.post("http://csvlint.io/package.json", { formData: {"files_data[]": content } }, function(error, response, body) {

      if (error) return reject(error);

      var packageURL = JSON.parse(response.body).package.url;
      var interval = setInterval(function() {
        request.get(packageURL + ".json", function(error, response, body) {
          try {
            var validationURL = JSON.parse(body).package.validations[0].url;
            clearInterval(interval);
            request.get(validationURL + ".json", function(error, response, body) {
              if (error) return reject(error);
              resolve(JSON.parse(body));
            });
          } catch(e) {}
        });
      }, 1000);

    });
  });
}

function validate() {
  data = hot.getData().map(function(d) { return d.join(",") }).join("\n")
  getValidation(data).then(function(json_validation) {
    errors = json_validation.validation.errors
    warnings = json_validation.validation.warnings
    info_messages = json_validation.validation.info
    console.error(errors)
    console.warn(warnings)
    console.info(info_messages);
  });
}
<|endoftext|>


<commit_before>'use strict'

module.exports = worker

// This function is somewhat special, it is run within a worker context.
function worker () {
  var indexedDB = self.indexedDB
  var db
  var methodMap = {
    connect: connect,
    disconnect: disconnect,
    create: create,
    update: update,
    delete: remove,
    deleteAll: removeAll
  }

  self.addEventListener('message', function (event) {
    var data = event.data
    var id = data.id
    var method = data.method

    methodMap[method](data, function (error, result, transfer) {
      if (error) {
        self.postMessage({
          id: id, error: error.toString()
        })
        return
      }

      self.postMessage({
        id: id, result: result
      }, transfer)
    })
  })

  function connect (data, callback) {
    var request = indexedDB.open(data.name)
    var typesArray = data.typesArray

    request.onerror = errorConnection
    request.onupgradeneeded = handleUpgrade
    request.onsuccess = handleSuccess

    function handleSuccess (event) {
      var i, j

      db = event.target.result

      for (i = 0, j = typesArray.length; i < j; i++)
        if (!~Array.prototype.indexOf.call(
          db.objectStoreNames, typesArray[i])) {
          reconnect()
          return
        }

      loadRecords()
    }

    function handleUpgrade (event) {
      var i, j, type

      db = event.target.result

      for (i = 0, j = typesArray.length; i < j; i++) {
        type = typesArray[i]
        if (!~Array.prototype.indexOf.call(db.objectStoreNames, type))
          db.createObjectStore(type, { keyPath: primaryKey })
      }

      for (i = 0, j = db.objectStoreNames.length; i < j; i++) {
        type = db.objectStoreNames[i]
        if (!~Array.prototype.indexOf.call(typesArray, type))
          db.deleteObjectStore(type)
      }
    }

    function reconnect () {
      var version = (db.version || 1) + 1

      db.close()
      request = indexedDB.open(data.name, version)
      request.onerror = errorReconnection
      request.onupgradeneeded = handleUpgrade
      request.onsuccess = function (event) {
        db = event.target.result
        loadRecords(db)
      }
    }

    function loadRecords () {
      var counter = 0
      var payload = {}
      var transfer = []
      var i, j

      for (i = 0, j = typesArray.length; i < j; i++)
        loadType(typesArray[i])

      function loadType (type) {
        var transaction = db.transaction(type, 'readonly')
        var objectStore = transaction.objectStore(type)
        var cursor = objectStore.openCursor()

        payload[type] = []
        cursor.onsuccess = function (event) {
          var iterator = event.target.result
          if (iterator) {
            payload[type].push(iterator.value[dataKey])
            transfer.push(iterator.value[dataKey].buffer)
            iterator.continue()
            return
          }
          counter++
          if (counter === typesArray.length)
            callback(null, payload, transfer)
        }
        cursor.onerror = errorIteration
      }
    }

    function errorConnection () {
      callback('The database connection could not be established.')
    }

    function errorReconnection () {
      callback('An attempt to reconnect failed.')
    }

    function errorIteration () {
      callback('Failed to read record.')
    }
  }

  function disconnect () {
    db.close()
  }

  function create (data, callback) {
    var recordsLength = Object.keys(data.records).length
    var type = data.type
    var transaction = db.transaction(type, 'readwrite')
    var objectStore = transaction.objectStore(type)
    var id, record, object, request, counter = 0

    for (id in data.records) {
      record = data.records[id]
      object = {}
      object[primaryKey] = type + delimiter + id
      object[dataKey] = record
      request = objectStore.add(object)
      request.onsuccess = check
      request.onerror = error
    }

    function check () {
      counter++
      if (counter === recordsLength) callback()
    }

    function error () {
      callback('A record could not be created.')
    }
  }

  function update (data, callback) {
    var recordsLength = Object.keys(data.records).length
    var type = data.type
    var transaction = db.transaction(type, 'readwrite')
    var objectStore = transaction.objectStore(type)
    var id, record, object, request, counter = 0

    for (id in data.records) {
      record = data.records[id]
      object = {}
      object[primaryKey] = type + delimiter + id
      object[dataKey] = record
      request = objectStore.put(object)
      request.onsuccess = check
      request.onerror = error
    }

    function check () {
      counter++
      if (counter === recordsLength) callback()
    }

    function error () {
      callback('A record could not be updated.')
    }
  }

  function remove (data, callback) {
    var type = data.type
    var ids = data.ids
    var transaction = db.transaction(type, 'readwrite')
    var objectStore = transaction.objectStore(type)
    var i, j, id, request, counter = 0

    for (i = 0, j = ids.length; i < j; i++) {
      id = ids[i]
      request = objectStore.delete(type + delimiter + id)
      request.onsuccess = check
      request.onerror = error
    }

    function check () {
      counter++
      if (counter === ids.length) callback()
    }

    function error () {
      callback('A record could not be deleted.')
    }
  }

  function removeAll (data, callback) {
    var type = data.type
    var transaction = db.transaction(type, 'readwrite')
    var objectStore = transaction.objectStore(type)
    var request = objectStore.clear()
    request.onsuccess = function () { callback() }
    request.onerror = error

    function error () {
      callback('Not all records could be deleted.')
    }
  }
}
<commit_msg>add check for indexeddb in web worker<commit_after>'use strict'

module.exports = worker

// This function is somewhat special, it is run within a worker context.
function worker () {
  var indexedDB = self.indexedDB
  var db
  var methodMap = {
    connect: connect,
    disconnect: disconnect,
    create: create,
    update: update,
    delete: remove,
    deleteAll: removeAll
  }

  if (!indexedDB)
    throw new Error('IndexedDB is unsupported in a Web Worker in this ' +
    'environment.')

  self.addEventListener('message', function (event) {
    var data = event.data
    var id = data.id
    var method = data.method

    methodMap[method](data, function (error, result, transfer) {
      if (error) {
        self.postMessage({
          id: id, error: error.toString()
        })
        return
      }

      self.postMessage({
        id: id, result: result
      }, transfer)
    })
  })

  function connect (data, callback) {
    var request = indexedDB.open(data.name)
    var typesArray = data.typesArray

    request.onerror = errorConnection
    request.onupgradeneeded = handleUpgrade
    request.onsuccess = handleSuccess

    function handleSuccess (event) {
      var i, j

      db = event.target.result

      for (i = 0, j = typesArray.length; i < j; i++)
        if (!~Array.prototype.indexOf.call(
          db.objectStoreNames, typesArray[i])) {
          reconnect()
          return
        }

      loadRecords()
    }

    function handleUpgrade (event) {
      var i, j, type

      db = event.target.result

      for (i = 0, j = typesArray.length; i < j; i++) {
        type = typesArray[i]
        if (!~Array.prototype.indexOf.call(db.objectStoreNames, type))
          db.createObjectStore(type, { keyPath: primaryKey })
      }

      for (i = 0, j = db.objectStoreNames.length; i < j; i++) {
        type = db.objectStoreNames[i]
        if (!~Array.prototype.indexOf.call(typesArray, type))
          db.deleteObjectStore(type)
      }
    }

    function reconnect () {
      var version = (db.version || 1) + 1

      db.close()
      request = indexedDB.open(data.name, version)
      request.onerror = errorReconnection
      request.onupgradeneeded = handleUpgrade
      request.onsuccess = function (event) {
        db = event.target.result
        loadRecords(db)
      }
    }

    function loadRecords () {
      var counter = 0
      var payload = {}
      var transfer = []
      var i, j

      for (i = 0, j = typesArray.length; i < j; i++)
        loadType(typesArray[i])

      function loadType (type) {
        var transaction = db.transaction(type, 'readonly')
        var objectStore = transaction.objectStore(type)
        var cursor = objectStore.openCursor()

        payload[type] = []
        cursor.onsuccess = function (event) {
          var iterator = event.target.result
          if (iterator) {
            payload[type].push(iterator.value[dataKey])
            transfer.push(iterator.value[dataKey].buffer)
            iterator.continue()
            return
          }
          counter++
          if (counter === typesArray.length)
            callback(null, payload, transfer)
        }
        cursor.onerror = errorIteration
      }
    }

    function errorConnection () {
      callback('The database connection could not be established.')
    }

    function errorReconnection () {
      callback('An attempt to reconnect failed.')
    }

    function errorIteration () {
      callback('Failed to read record.')
    }
  }

  function disconnect () {
    db.close()
  }

  function create (data, callback) {
    var recordsLength = Object.keys(data.records).length
    var type = data.type
    var transaction = db.transaction(type, 'readwrite')
    var objectStore = transaction.objectStore(type)
    var id, record, object, request, counter = 0

    for (id in data.records) {
      record = data.records[id]
      object = {}
      object[primaryKey] = type + delimiter + id
      object[dataKey] = record
      request = objectStore.add(object)
      request.onsuccess = check
      request.onerror = error
    }

    function check () {
      counter++
      if (counter === recordsLength) callback()
    }

    function error () {
      callback('A record could not be created.')
    }
  }

  function update (data, callback) {
    var recordsLength = Object.keys(data.records).length
    var type = data.type
    var transaction = db.transaction(type, 'readwrite')
    var objectStore = transaction.objectStore(type)
    var id, record, object, request, counter = 0

    for (id in data.records) {
      record = data.records[id]
      object = {}
      object[primaryKey] = type + delimiter + id
      object[dataKey] = record
      request = objectStore.put(object)
      request.onsuccess = check
      request.onerror = error
    }

    function check () {
      counter++
      if (counter === recordsLength) callback()
    }

    function error () {
      callback('A record could not be updated.')
    }
  }

  function remove (data, callback) {
    var type = data.type
    var ids = data.ids
    var transaction = db.transaction(type, 'readwrite')
    var objectStore = transaction.objectStore(type)
    var i, j, id, request, counter = 0

    for (i = 0, j = ids.length; i < j; i++) {
      id = ids[i]
      request = objectStore.delete(type + delimiter + id)
      request.onsuccess = check
      request.onerror = error
    }

    function check () {
      counter++
      if (counter === ids.length) callback()
    }

    function error () {
      callback('A record could not be deleted.')
    }
  }

  function removeAll (data, callback) {
    var type = data.type
    var transaction = db.transaction(type, 'readwrite')
    var objectStore = transaction.objectStore(type)
    var request = objectStore.clear()
    request.onsuccess = function () { callback() }
    request.onerror = error

    function error () {
      callback('Not all records could be deleted.')
    }
  }
}
<|endoftext|>