<commit_before>const LOG_2 = Math.log(2);
const START_SIZE = 32;
const ITERATIONS_PER_CHUNK = 100;

let state = {
    ctx: null,
    t_ctx: null,

    width: 0,
    height: 0,

    smooth: false,
    colors: 32,
    color_shift: 0,

    data_url: null,
    save_when_done: false,

    compute: {
        i: 0,
        j: 0,
        size: 0,
        max_iters: 0,
        origin_x: 0,
        origin_y: 0,
        width: 0,
        height: 0,
    },
};

let last_animation_request = 0;

function Start(fullscreen) {

    let mc_selector = $('#main_canvas');
    let width = Math.floor(mc_selector.width() / 2) * 2;
    let mc = mc_selector.get(0);

    if(fullscreen === true) {
        mc.height = Math.floor(mc_selector.height() / 2) * 2;
    } else {
        // Set the canvas height based on the width
        mc.height = width;
    }
    mc.width = width;

    let tc_selector = $('#tracer_canvas');
    let tc = tc_selector.get(0);
    tc.width = mc.width;
    tc.height = mc.height;

    state.width = mc.width;
    state.height = mc.height;

    state.ctx = mc.getContext('2d');
    state.t_ctx = tc.getContext('2d');

    // mc_selector.click(Zoom);
    tc_selector.click(Zoom);
    tc_selector.contextmenu(Zoom);

    window.onkeypress = KeyPress;

    Reset();

    ReDraw();
}

function KeyPress(e) {

    if(e.key === 'r') {
        Reset();
        ReDraw();
    } else if(e.key === 's') {
        Smooth();
    } else if(e.key === 'v') {
        Save();
    } else if(e.key === 'V') {
        SaveWhenDone();
    } else if(e.key === '+') {
        state.compute.max_iters *= 10;
        ReDraw();
    } else if(e.key === '-') {
        state.compute.max_iters /= 10;
        ReDraw();
    } else if(e.key === 'j') {
        state.colors -= 4;
        if(state.colors < 1) state.colors = 1;
        ReDraw();
    } else if(e.key === 'k') {
        if(state.colors <= 1) state.colors = 0;
        state.colors += 4;
        ReDraw();
    } else if(e.key === 'l') {
        ShiftColors(4/360);
    } else if(e.key === 'h') {
        ShiftColors(-4/360);
    }
}

function Done() {

    console.log("DONE COMPUTING");

    last_animation_request = 0;

    state.data_url = $('#main_canvas').get(0).toDataURL('image/png');
    if(state.save_when_done) {
        state.save_when_done = false;
        Save();
    }
}

function Save() {

    let data_url;
    if('data_url' in state) {
        data_url = state.data_url;
    } else {
        data_url = $('#main_canvas').get(0).toDataURL('image/png');
    }

    let dl_anchor = $('#fractal-download').get(0);
    dl_anchor.setAttribute('href', data_url);
    dl_anchor.click();
}

function SaveWhenDone() {
    if(state.data_url != null) {
        Save();
    } else {
        alert("Will prompt you to save the image when it's done computing.");
        state.save_when_done = true;
    }
}

function QueueDraw() {
    last_animation_request = window.requestAnimationFrame(Draw);
}

function Reset() {

    let mc = $('#main_canvas').get(0);

    state.compute.origin_x = 0;
    state.compute.origin_y = 0;
    state.compute.max_iters = 10000;
    state.compute.height = 4.0
    state.compute.width = state.compute.height * state.width / state.height;

    state.data_url = null;
}

function Smooth() {
    state.smooth = !state.smooth;
    ReDraw();
}

function Zoom(e) {

    let i = e.offsetX;
    let j = e.offsetY;

    state.compute.origin_x = state.compute.origin_x + ((i - (state.width / 2)) * state.compute.width / state.width);
    state.compute.origin_y = state.compute.origin_y + ((j - (state.height / 2)) * state.compute.height / state.height);

    if(e.button === 0) {
        state.compute.width /= 2;
        state.compute.height /= 2;
    } else if(e.button === 2) {
        state.compute.width *= 2;
        state.compute.height *= 2;
    }

    ReDraw();

    return false;
}

function ShiftColors(angle) {

    state.color_shift += angle;

    let rendering = state.ctx.getImageData(0, 0, state.width, state.height);
    let img_dat = rendering.data;

    let r,g,b;
    for(let p = 0; p < img_dat.length; p+=4) {
        r = img_dat[p]
        g = img_dat[p+1];
        b = img_dat[p+2];
        // alpha channel (p+3) is ignored

        let hsl = rgbToHsl(r, g, b);
        let rgb = hslToRgb((hsl[0] + angle) % 1, hsl[1], hsl[2]);

        img_dat[p] = rgb[0];
        img_dat[p+1] = rgb[1];
        img_dat[p+2] = rgb[2];
    }

    state.ctx.putImageData(rendering,0,0);
}

function ReDraw() {

    if(last_animation_request != 0) {
        window.cancelAnimationFrame(last_animation_request);
    }

    // state.img_dat = state.ctx.createImageData(2, 2);

    state.data_url = null;
    state.save_when_done = false;

    state.compute.i = 0;
    state.compute.j = 0;
    state.compute.size = START_SIZE;

    state.ctx.fillStyle = '#000000';
    state.ctx.fillRect(0, 0, state.width, state.height);
    state.t_ctx.clearRect(0, 0, state.width, state.height);

    QueueDraw();
}

function createMatrix(width, height){
    return Array(height).map(column => Array(width));
}

function Draw() {

    let ctx = state.ctx;
    let c = state.compute;

    // let start_time = (new Date()).getTime();

    let field_x = state.width / 2;
    let field_y = state.height / 2;

    if(c.i === 0 && c.j === 0) {
        state.t_ctx.fillStyle = 'rgba(255,255,255,255)';
        state.t_ctx.fillRect(field_x + c.size, 0, 1, state.height);
        state.t_ctx.fillRect(field_x - c.size, 0, 1, state.height);
    }

    for(let iterator = 0; iterator < ITERATIONS_PER_CHUNK; iterator++) {

        let x1 = field_x + c.i * c.size;
        let x2 = field_x - (c.i+1) * c.size;
        let y = c.j * c.size;

        if(c.size === START_SIZE || (0 < c.i & 0x1) || (0 < c.j & 0x1)) {

            RenderBlock(ctx, c.origin_x, c.origin_y, c.width, c.height, x1, y, state.width, state.height, c.size);
            RenderBlock(ctx, c.origin_x, c.origin_y, c.width, c.height, x2, y, state.width, state.height, c.size);
        }

        c.j++;
        if(state.height < c.j * c.size) {

            c.j = 0;

            state.t_ctx.fillStyle = 'rgba(0,0,0,0)';
            state.t_ctx.clearRect(field_x + (c.i + 1)*c.size, 0, 1, state.height);
            state.t_ctx.clearRect(field_x - (c.i + 1)*c.size, 0, 1, state.height);

            c.i++;
            if(state.width < c.i * c.size * 2) {

                if(c.size === 1) {
                    Done();
                    return;
                }

                c.size /= 2;
                c.i = 0;
            }

            state.t_ctx.fillStyle = 'rgba(255,255,255,255)';
            state.t_ctx.fillRect(field_x + (c.i + 1)*c.size, 0, 1, state.height);
            state.t_ctx.fillRect(field_x - (c.i + 1)*c.size, 0, 1, state.height);
        }
    }

    QueueDraw();
}

function ComputeEscape(c, d) {

    // iterate or z as:
    //      Z = Z^2 + C
    // where
    //      Z = a + bi
    //      C = c + di
    let a = 0;
    let b = 0;

    let iterations = 0;
    while((a*a) + (b*b) < 4 && iterations < state.compute.max_iters) {

        let n_a = a*a - b*b + c;
        let n_b = 2*a*b + d;

        a = n_a;
        b = n_b;

        iterations++;
    }

    // If smooth coloring is enabled then iterate a few more times to give a better color result
    if(state.smooth) {
        for(let k = 0; k < 3; k++) {
            let n_a = a*a - b*b + c;
            let n_b = 2*a*b + d;

            a = n_a;
            b = n_b;
        }

        if(iterations < state.compute.max_iters) {
            iterations = iterations + 3 - (Math.log(Math.log(Math.sqrt(a*a + b*b)))/LOG_2);
        }
    }

    return iterations;
}

function RenderBlock(ctx, x, y, width, height, field_x, field_y, field_width, field_height, size) {

    let iterations = ComputeEscape(
            x + width*(field_x/field_width - 0.5),
            y + height*(field_y/field_height - 0.5)
        );

    let color = '#000000';
    if(iterations < state.compute.max_iters) {
        color = 'hsl(' + (180 + 360*(state.color_shift + iterations/state.colors)) + ', 50%, 50%)';
    }

    ctx.fillStyle = color;
    ctx.fillRect(field_x, field_y, size, size);
    // } else {

    //     let img_dat = state.img_dat;
    //     let img_dat_raw = img_dat.data;
    //     if(cu.size * cu.size * 4 < img_dat_raw.length) {
    //         img_dat = ctx.createImageData(cu.size, cu.size);
    //         state.img_dat = img_dat;
    //         img_dat_raw = img_dat.data;
    //     }

    //     let color = [0, 0, 0];
    //     if(iterations < state.max_iters) {
    //         let h = iterations;
    //         if(state.smooth) {
    //             h = iterations + 3 - (Math.log(Math.log(Math.sqrt(a*a + b*b)))/LOG_2);
    //         }
    //         color = hslToRgb(((180 + h * 360 / state.colors) % 360) / 360, 0.5, 0.5);
    //     }

    //     for(let i = 0; i < img_dat_raw.length; i += 4) {
    //         img_dat_raw[i + 0] = color[0];
    //         img_dat_raw[i + 1] = color[1];
    //         img_dat_raw[i + 2] = color[2];
    //         img_dat_raw[i + 3] = 255;
    //     }
    //     ctx.putImageData(img_dat, cu.i, cu.j);
    // }
}

/**
 * Gotten from: http://stackoverflow.com/a/9493060
 *
 * Converts an HSL color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes h, s, and l are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 255].
 *
 * @param   {number}  h       The hue
 * @param   {number}  s       The saturation
 * @param   {number}  l       The lightness
 * @return  {Array}           The RGB representation
 */
function hslToRgb(h, s, l){
    var r, g, b;

    if(s == 0){
        r = g = b = l; // achromatic
    }else{
        var hue2rgb = function hue2rgb(p, q, t){
            if(t < 0) t += 1;
            if(t > 1) t -= 1;
            if(t < 1/6) return p + (q - p) * 6 * t;
            if(t < 1/2) return q;
            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        }

        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
    }

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}

/**
 * Gotten from: http://stackoverflow.com/a/9493060
 *
 * Converts an RGB color value to HSL. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes r, g, and b are contained in the set [0, 255] and
 * returns h, s, and l in the set [0, 1].
 *
 * @param   {number}  r       The red color value
 * @param   {number}  g       The green color value
 * @param   {number}  b       The blue color value
 * @return  {Array}           The HSL representation
 */
function rgbToHsl(r, g, b){
    r /= 255, g /= 255, b /= 255;
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;

    if(max == min){
        h = s = 0; // achromatic
    }else{
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch(max){
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }

    return [h, s, l];
}
<commit_msg>WE ALL MAKE LOGIC MISTAKES SOMETIMES<commit_after>const LOG_2 = Math.log(2);
const START_SIZE = 32;
const ITERATIONS_PER_CHUNK = 100;

let state = {
    ctx: null,
    t_ctx: null,

    width: 0,
    height: 0,

    smooth: false,
    colors: 32,
    color_shift: 0,

    data_url: null,
    save_when_done: false,

    compute: {
        i: 0,
        j: 0,
        size: 0,
        max_iters: 0,
        origin_x: 0,
        origin_y: 0,
        width: 0,
        height: 0,
    },
};

let last_animation_request = 0;

function Start(fullscreen) {

    let mc_selector = $('#main_canvas');
    let width = Math.floor(mc_selector.width() / 2) * 2;
    let mc = mc_selector.get(0);

    if(fullscreen === true) {
        mc.height = Math.floor(mc_selector.height() / 2) * 2;
    } else {
        // Set the canvas height based on the width
        mc.height = width;
    }
    mc.width = width;

    let tc_selector = $('#tracer_canvas');
    let tc = tc_selector.get(0);
    tc.width = mc.width;
    tc.height = mc.height;

    state.width = mc.width;
    state.height = mc.height;

    state.ctx = mc.getContext('2d');
    state.t_ctx = tc.getContext('2d');

    // mc_selector.click(Zoom);
    tc_selector.click(Zoom);
    tc_selector.contextmenu(Zoom);

    window.onkeypress = KeyPress;

    Reset();

    ReDraw();
}

function KeyPress(e) {

    if(e.key === 'r') {
        Reset();
        ReDraw();
    } else if(e.key === 's') {
        Smooth();
    } else if(e.key === 'v') {
        Save();
    } else if(e.key === 'V') {
        SaveWhenDone();
    } else if(e.key === '+') {
        state.compute.max_iters *= 10;
        ReDraw();
    } else if(e.key === '-') {
        state.compute.max_iters /= 10;
        ReDraw();
    } else if(e.key === 'j') {
        state.colors -= 4;
        if(state.colors < 1) state.colors = 1;
        ReDraw();
    } else if(e.key === 'k') {
        if(state.colors <= 1) state.colors = 0;
        state.colors += 4;
        ReDraw();
    } else if(e.key === 'l') {
        ShiftColors(4/360);
    } else if(e.key === 'h') {
        ShiftColors(-4/360);
    }
}

function Done() {

    console.log("DONE COMPUTING");

    last_animation_request = 0;

    state.data_url = $('#main_canvas').get(0).toDataURL('image/png');
    if(state.save_when_done) {
        state.save_when_done = false;
        Save();
    }
}

function Save() {

    let data_url;
    if('data_url' in state) {
        data_url = state.data_url;
    } else {
        data_url = $('#main_canvas').get(0).toDataURL('image/png');
    }

    let dl_anchor = $('#fractal-download').get(0);
    dl_anchor.setAttribute('href', data_url);
    dl_anchor.click();
}

function SaveWhenDone() {
    if(state.data_url != null) {
        Save();
    } else {
        alert("Will prompt you to save the image when it's done computing.");
        state.save_when_done = true;
    }
}

function QueueDraw() {
    last_animation_request = window.requestAnimationFrame(Draw);
}

function Reset() {

    let mc = $('#main_canvas').get(0);

    state.compute.origin_x = 0;
    state.compute.origin_y = 0;
    state.compute.max_iters = 10000;
    state.compute.height = 4.0
    state.compute.width = state.compute.height * state.width / state.height;

    state.data_url = null;
}

function Smooth() {
    state.smooth = !state.smooth;
    ReDraw();
}

function Zoom(e) {

    let i = e.offsetX;
    let j = e.offsetY;

    state.compute.origin_x = state.compute.origin_x + ((i - (state.width / 2)) * state.compute.width / state.width);
    state.compute.origin_y = state.compute.origin_y + ((j - (state.height / 2)) * state.compute.height / state.height);

    if(e.button === 0) {
        state.compute.width /= 2;
        state.compute.height /= 2;
    } else if(e.button === 2) {
        state.compute.width *= 2;
        state.compute.height *= 2;
    }

    ReDraw();

    return false;
}

function ShiftColors(angle) {

    state.color_shift += angle;

    let rendering = state.ctx.getImageData(0, 0, state.width, state.height);
    let img_dat = rendering.data;

    let r,g,b;
    for(let p = 0; p < img_dat.length; p+=4) {
        r = img_dat[p]
        g = img_dat[p+1];
        b = img_dat[p+2];
        // alpha channel (p+3) is ignored

        let hsl = rgbToHsl(r, g, b);
        let rgb = hslToRgb((hsl[0] + angle) % 1, hsl[1], hsl[2]);

        img_dat[p] = rgb[0];
        img_dat[p+1] = rgb[1];
        img_dat[p+2] = rgb[2];
    }

    state.ctx.putImageData(rendering,0,0);
}

function ReDraw() {

    if(last_animation_request != 0) {
        window.cancelAnimationFrame(last_animation_request);
    }

    // state.img_dat = state.ctx.createImageData(2, 2);

    state.data_url = null;
    state.save_when_done = false;

    state.compute.i = 0;
    state.compute.j = 0;
    state.compute.size = START_SIZE;

    state.ctx.fillStyle = '#000000';
    state.ctx.fillRect(0, 0, state.width, state.height);
    state.t_ctx.clearRect(0, 0, state.width, state.height);

    QueueDraw();
}

function createMatrix(width, height){
    return Array(height).map(column => Array(width));
}

function Draw() {

    let ctx = state.ctx;
    let c = state.compute;

    // let start_time = (new Date()).getTime();

    let field_x = state.width / 2;
    let field_y = state.height / 2;

    if(c.i === 0 && c.j === 0) {
        state.t_ctx.fillStyle = 'rgba(255,255,255,255)';
        state.t_ctx.fillRect(field_x + c.size, 0, 1, state.height);
        state.t_ctx.fillRect(field_x - c.size, 0, 1, state.height);
    }

    for(let iterator = 0; iterator < ITERATIONS_PER_CHUNK; iterator++) {

        let x1 = field_x + c.i * c.size;
        let x2 = field_x - (c.i+1) * c.size;
        let y = c.j * c.size;

        let shared_condition = c.size === START_SIZE || (0 < (c.j & 0x1));
        if(shared_condition || (c.i & 0x1) === 1) {
            RenderBlock(ctx, c.origin_x, c.origin_y, c.width, c.height, x1, y, state.width, state.height, c.size);
        }
        if(shared_condition || (c.i & 0x1) === 0) {
            RenderBlock(ctx, c.origin_x, c.origin_y, c.width, c.height, x2, y, state.width, state.height, c.size);
        }

        c.j++;
        if(state.height < c.j * c.size) {

            c.j = 0;

            state.t_ctx.fillStyle = 'rgba(0,0,0,0)';
            state.t_ctx.clearRect(field_x + (c.i + 1)*c.size, 0, 1, state.height);
            state.t_ctx.clearRect(field_x - (c.i + 1)*c.size, 0, 1, state.height);

            c.i++;
            if(state.width < c.i * c.size * 2) {

                if(c.size === 1) {
                    Done();
                    return;
                }

                c.size /= 2;
                c.i = 0;
            }

            state.t_ctx.fillStyle = 'rgba(255,255,255,255)';
            state.t_ctx.fillRect(field_x + (c.i + 1)*c.size, 0, 1, state.height);
            state.t_ctx.fillRect(field_x - (c.i + 1)*c.size, 0, 1, state.height);
        }
    }

    QueueDraw();
}

function ComputeEscape(c, d) {

    // iterate or z as:
    //      Z = Z^2 + C
    // where
    //      Z = a + bi
    //      C = c + di
    let a = 0;
    let b = 0;

    let iterations = 0;
    while((a*a) + (b*b) < 4 && iterations < state.compute.max_iters) {

        let n_a = a*a - b*b + c;
        let n_b = 2*a*b + d;

        a = n_a;
        b = n_b;

        iterations++;
    }

    // If smooth coloring is enabled then iterate a few more times to give a better color result
    if(state.smooth) {
        for(let k = 0; k < 3; k++) {
            let n_a = a*a - b*b + c;
            let n_b = 2*a*b + d;

            a = n_a;
            b = n_b;
        }

        if(iterations < state.compute.max_iters) {
            iterations = iterations + 3 - (Math.log(Math.log(Math.sqrt(a*a + b*b)))/LOG_2);
        }
    }

    return iterations;
}

function RenderBlock(ctx, x, y, width, height, field_x, field_y, field_width, field_height, size) {

    let iterations = ComputeEscape(
            x + width*(field_x/field_width - 0.5),
            y + height*(field_y/field_height - 0.5)
        );

    let color = '#000000';
    if(iterations < state.compute.max_iters) {
        color = 'hsl(' + (180 + 360*(state.color_shift + iterations/state.colors)) + ', 50%, 50%)';
    }

    ctx.fillStyle = color;
    ctx.fillRect(field_x, field_y, size, size);
    // } else {

    //     let img_dat = state.img_dat;
    //     let img_dat_raw = img_dat.data;
    //     if(cu.size * cu.size * 4 < img_dat_raw.length) {
    //         img_dat = ctx.createImageData(cu.size, cu.size);
    //         state.img_dat = img_dat;
    //         img_dat_raw = img_dat.data;
    //     }

    //     let color = [0, 0, 0];
    //     if(iterations < state.max_iters) {
    //         let h = iterations;
    //         if(state.smooth) {
    //             h = iterations + 3 - (Math.log(Math.log(Math.sqrt(a*a + b*b)))/LOG_2);
    //         }
    //         color = hslToRgb(((180 + h * 360 / state.colors) % 360) / 360, 0.5, 0.5);
    //     }

    //     for(let i = 0; i < img_dat_raw.length; i += 4) {
    //         img_dat_raw[i + 0] = color[0];
    //         img_dat_raw[i + 1] = color[1];
    //         img_dat_raw[i + 2] = color[2];
    //         img_dat_raw[i + 3] = 255;
    //     }
    //     ctx.putImageData(img_dat, cu.i, cu.j);
    // }
}

/**
 * Gotten from: http://stackoverflow.com/a/9493060
 *
 * Converts an HSL color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes h, s, and l are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 255].
 *
 * @param   {number}  h       The hue
 * @param   {number}  s       The saturation
 * @param   {number}  l       The lightness
 * @return  {Array}           The RGB representation
 */
function hslToRgb(h, s, l){
    var r, g, b;

    if(s == 0){
        r = g = b = l; // achromatic
    }else{
        var hue2rgb = function hue2rgb(p, q, t){
            if(t < 0) t += 1;
            if(t > 1) t -= 1;
            if(t < 1/6) return p + (q - p) * 6 * t;
            if(t < 1/2) return q;
            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        }

        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
    }

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}

/**
 * Gotten from: http://stackoverflow.com/a/9493060
 *
 * Converts an RGB color value to HSL. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes r, g, and b are contained in the set [0, 255] and
 * returns h, s, and l in the set [0, 1].
 *
 * @param   {number}  r       The red color value
 * @param   {number}  g       The green color value
 * @param   {number}  b       The blue color value
 * @return  {Array}           The HSL representation
 */
function rgbToHsl(r, g, b){
    r /= 255, g /= 255, b /= 255;
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;

    if(max == min){
        h = s = 0; // achromatic
    }else{
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch(max){
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }

    return [h, s, l];
}
<|endoftext|>


<commit_before><commit_msg>Teaching today to know what month it is in<commit_after>var date = new Date(); // e.g. Thu Jul 23 2015 14:28:07 GMT-0500 (CDT)

var monthToNum = {
  'Jan' : 1,
  'Feb' : 2,
  'Mar' : 3,
  'Apr' : 4,
  'May' : 5,
  'Jun' : 6,
  'Jul' : 7,
  'Aug' : 8,
  'Sep' : 9,
  'Oct' : 10,
  'Nov' : 11,
  'Dec' : 12
};

var today = {
  month : monthToNum[date.toString().split(' ')[1]]
};

module.exports = today;
<|endoftext|>


<commit_before>import find from 'lodash.find';
import composedFetch from '../composedFetch.js';

export const sourcePostalPoint = id => composedFetch(id)
  .then(data => {
    const result = find(data.events, { key: `received.domestic-corner` });
    return result.location.en;
  })
  .catch(e => console.error(e));

export const destinationPostalPoint = id => composedFetch(id)
  .then(data => data.deliveryPoint.name.en)
  .catch(e => console.error(e));

export const destinationAsAddress = id => composedFetch(id)
  .then(data => {
    const obj = data.deliveryPoint;
    return `${obj.street.en} ${obj.streetNumber.en}, ${obj.postcode.en} ${obj.municipality.en}`;
  })
  .catch(e => console.error(e));

export const openingHours = (id, day) => {
  if (!day) throw new Error(`Please specify a day`);

  return composedFetch(id)
    .then(data => {
      const obj = find(
        data.deliveryPoint.openingSchedules,
        { dayOfTheWeek: day.toUpperCase() }
      );

      return obj.openingHours[0];
    })
    .catch(e => console.error(e));
};

export const destinationCoordinate = id => composedFetch(id)
  .then(data => {
    return {
      latitude: data.deliveryPoint.latitude,
      longitude: data.deliveryPoint.longitude,
    };
  })
  .catch(e => console.error(e));
<commit_msg>:art: Throw TypeError instead of Error<commit_after>import find from 'lodash.find';
import composedFetch from '../composedFetch.js';

export const sourcePostalPoint = id => composedFetch(id)
  .then(data => {
    const result = find(data.events, { key: `received.domestic-corner` });
    return result.location.en;
  })
  .catch(e => console.error(e));

export const destinationPostalPoint = id => composedFetch(id)
  .then(data => data.deliveryPoint.name.en)
  .catch(e => console.error(e));

export const destinationAsAddress = id => composedFetch(id)
  .then(data => {
    const obj = data.deliveryPoint;
    return `${obj.street.en} ${obj.streetNumber.en}, ${obj.postcode.en} ${obj.municipality.en}`;
  })
  .catch(e => console.error(e));

export const openingHours = (id, day) => {
  if (!day) throw new TypeError(`Please specify a day`);

  return composedFetch(id)
    .then(data => {
      const obj = find(
        data.deliveryPoint.openingSchedules,
        { dayOfTheWeek: day.toUpperCase() }
      );

      return obj.openingHours[0];
    })
    .catch(e => console.error(e));
};

export const destinationCoordinate = id => composedFetch(id)
  .then(data => {
    return {
      latitude: data.deliveryPoint.latitude,
      longitude: data.deliveryPoint.longitude,
    };
  })
  .catch(e => console.error(e));
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {
  Text,
  View,
  TouchableOpacity,
  StyleSheet,
  Alert,
  Platform
} from 'react-native';
import {Navigation} from 'react-native-navigation';

export default class FirstTabScreen extends Component {
  static navigatorButtons = {
    leftButtons: [{
      icon: require('../../img/navicon_menu.png'),
      id: 'menu'
    }],
    rightButtons: [
      {
        title: 'Edit',
        id: 'edit',
        showAsAction: 'always'
      },
      {
        icon: require('../../img/navicon_add.png'),
        id: 'add',
        badgeStyle: 'purple'
      }
    ]
  };
  static navigatorStyle = {
    navBarBackgroundColor: '#4dbce9',
    navBarTextColor: '#ffff00',
    navBarSubtitleTextColor: '#ff0000',
    navBarButtonColor: '#ffffff',
    statusBarTextColorScheme: 'light',
    tabBarBackgroundColor: '#4dbce9',
    tabBarButtonColor: '#ffffff',
    tabBarSelectedButtonColor: '#ffff00'
  };

  constructor(props) {
    super(props);
    this.state = {
      badge: 0
    }

    // if you want to listen on navigator events, set this up
    this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
  }

  onNavigatorEvent(event) {
    if (event.id === 'menu') {
      this.props.navigator.toggleDrawer({
        side: 'left',
        animated: true
      });
    }
    if (event.id === 'edit') {
      Alert.alert('NavBar', 'Edit button pressed');
    }
    if (event.id === 'add') {
      Alert.alert('NavBar', 'Add button pressed');
    }
  }

  render() {
    return (
      <View style={{flex: 1, padding: 20}}>
        <TouchableOpacity onPress={ this.onPushPress.bind(this) }>
          <Text style={styles.button}>Push Plain Screen</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onPushStyledPress.bind(this) }>
          <Text style={styles.button}>Push Styled Screen</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onModalPress.bind(this) }>
          <Text style={styles.button}>Show Modal Screen</Text>
        </TouchableOpacity>

        {
          Platform.OS === 'ios' ?
            <TouchableOpacity onPress={ this.onLightBoxPress.bind(this) }>
              <Text style={styles.button}>Show LightBox</Text>
            </TouchableOpacity> : false
        }

        {
          Platform.OS === 'ios' ?
            <TouchableOpacity onPress={ this.onInAppNotificationPress.bind(this) }>
              <Text style={styles.button}>Show In-App Notification</Text>
            </TouchableOpacity> : false
        }

        <TouchableOpacity onPress={ this.onStartSingleScreenApp.bind(this) }>
          <Text style={styles.button}>Show Single Screen App</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onIncrementBadge.bind(this) }>
          <Text style={styles.button}>Increment Badge</Text>
        </TouchableOpacity>
      </View>
    );
  }

  onPushPress() {
    this.props.navigator.push({
      title: "More",
      screen: "example.PushedScreen"
    });
  }

  onPushStyledPress() {
    this.props.navigator.push({
      title: "Styled",
      screen: "example.StyledScreen"
    });
  }

  onModalPress() {
    this.props.navigator.showModal({
      title: "Modal",
      screen: "example.ModalScreen"
    });
  }

  onIncrementBadge() {
    this.props.navigator.setButtonBadge({
      buttonIndex: 0,
      badge: this.state.badge
    })

    let newBadgeValue = this.state.badge + 1

    this.setState({
      badge: newBadgeValue
    })
  }

  onLightBoxPress() {
    this.props.navigator.showLightBox({
      screen: "example.LightBoxScreen",
      style: {
        backgroundBlur: "dark"
      },
      passProps: {
        greeting: 'hey there'
      },
    });
  }

  onInAppNotificationPress() {
    this.props.navigator.showInAppNotification({
      screen: "example.NotificationScreen"
    });
  }

  onStartSingleScreenApp() {
    Navigation.startSingleScreenApp({
      screen: {
        screen: 'example.FirstTabScreen'
      },
      drawer: {
        left: {
          screen: 'example.SideMenu'
        }
      }
    });
  }
}

const styles = StyleSheet.create({
  button: {
    textAlign: 'center',
    fontSize: 18,
    marginBottom: 10,
    marginTop: 10,
    color: 'blue'
  }
});
<commit_msg>Example edit<commit_after>import React, {Component} from 'react';
import {
  Text,
  View,
  TouchableOpacity,
  StyleSheet,
  Alert,
  Platform
} from 'react-native';
import {Navigation} from 'react-native-navigation';

export default class FirstTabScreen extends Component {
  static navigatorButtons = {
    leftButtons: [{
      icon: require('../../img/navicon_menu.png'),
      id: 'menu'
    }],
    rightButtons: [
      {
        title: 'Edit',
        id: 'edit',
        showAsAction: 'always'
      },
      {
        icon: require('../../img/navicon_add.png'),
        id: 'add',
        badgeStyle: 'red',
        badgeCount: 0
      }
    ]
  };
  static navigatorStyle = {
    navBarBackgroundColor: '#4dbce9',
    navBarTextColor: '#ffff00',
    navBarSubtitleTextColor: '#ff0000',
    navBarButtonColor: '#ffffff',
    statusBarTextColorScheme: 'light',
    tabBarBackgroundColor: '#4dbce9',
    tabBarButtonColor: '#ffffff',
    tabBarSelectedButtonColor: '#ffff00'
  };

  constructor(props) {
    super(props);
    this.state = {
      badge: 0
    }

    // if you want to listen on navigator events, set this up
    this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
  }

  onNavigatorEvent(event) {
    if (event.id === 'menu') {
      this.props.navigator.toggleDrawer({
        side: 'left',
        animated: true
      });
    }
    if (event.id === 'edit') {
      Alert.alert('NavBar', 'Edit button pressed');
    }
    if (event.id === 'add') {
      Alert.alert('NavBar', 'Add button pressed');
    }
  }

  render() {
    return (
      <View style={{flex: 1, padding: 20}}>
        <TouchableOpacity onPress={ this.onPushPress.bind(this) }>
          <Text style={styles.button}>Push Plain Screen</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onPushStyledPress.bind(this) }>
          <Text style={styles.button}>Push Styled Screen</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onModalPress.bind(this) }>
          <Text style={styles.button}>Show Modal Screen</Text>
        </TouchableOpacity>

        {
          Platform.OS === 'ios' ?
            <TouchableOpacity onPress={ this.onLightBoxPress.bind(this) }>
              <Text style={styles.button}>Show LightBox</Text>
            </TouchableOpacity> : false
        }

        {
          Platform.OS === 'ios' ?
            <TouchableOpacity onPress={ this.onInAppNotificationPress.bind(this) }>
              <Text style={styles.button}>Show In-App Notification</Text>
            </TouchableOpacity> : false
        }

        <TouchableOpacity onPress={ this.onStartSingleScreenApp.bind(this) }>
          <Text style={styles.button}>Show Single Screen App</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onIncrementBadge.bind(this) }>
          <Text style={styles.button}>Increment Badge</Text>
        </TouchableOpacity>
      </View>
    );
  }

  onPushPress() {
    this.props.navigator.push({
      title: "More",
      screen: "example.PushedScreen"
    });
  }

  onPushStyledPress() {
    this.props.navigator.push({
      title: "Styled",
      screen: "example.StyledScreen"
    });
  }

  onModalPress() {
    this.props.navigator.showModal({
      title: "Modal",
      screen: "example.ModalScreen"
    });
  }

  onIncrementBadge() {
    let newBadgeValue = this.state.badge + 1

    this.setState({
      badge: newBadgeValue
    })

    this.props.navigator.setButtonBadge({
      buttonIndex: 0,
      badge: this.state.badge
    })
  }

  onLightBoxPress() {
    this.props.navigator.showLightBox({
      screen: "example.LightBoxScreen",
      style: {
        backgroundBlur: "dark"
      },
      passProps: {
        greeting: 'hey there'
      },
    });
  }

  onInAppNotificationPress() {
    this.props.navigator.showInAppNotification({
      screen: "example.NotificationScreen"
    });
  }

  onStartSingleScreenApp() {
    Navigation.startSingleScreenApp({
      screen: {
        screen: 'example.FirstTabScreen'
      },
      drawer: {
        left: {
          screen: 'example.SideMenu'
        }
      }
    });
  }
}

const styles = StyleSheet.create({
  button: {
    textAlign: 'center',
    fontSize: 18,
    marginBottom: 10,
    marginTop: 10,
    color: 'blue'
  }
});
<|endoftext|>


<commit_before>/**
 * Octopress-compatible filters.
 */

var path = require('path');

var base = require('./base');
var _ = require('lodash');

/**
 * Allow non-camelcase naming:
 */
/*jscs:disable requireCamelCaseOrUpperCaseIdentifiers*/

exports.condense_spaces = base.condenseSpaces;

exports.shorthand_url = base.extract.bind(null, /https?:\/\/(\S+)/);
exports.strip_slash = base.extract.bind(null, /(.+|^)\/$/);
exports.titlecase = base.titlecase;

exports.category_links = function(categories) {
  var output = '';

  if (categories) {
    if (!Array.isArray(categories)) {
      categories = [categories];
    }

    /**
     * Sort the list of categories, create a link for each one, and then
     * create a comma-separated list of all the links:
     */

    output = _.sortBy(categories)
      .map(exports.category_link)
      .join(', ');
  }
  return output;
};

exports.category_link = function(category) {
  var dir = exports.options.categoryDir || '';
  var url = path.join(dir, encodeURIComponent(category));

  return '' + category + '';
};

exports.expand_urls = function(s, url) {
  return base.replace(
    '(\\s+(href|src)\\s*=\\s*["|\']{1})(/[^/>]{1}[^"\'>]*)',
    '$1' + ((!url || url === '') ? '/' : url) + '$3',
    s
  );
};

exports.raw_content = base.extract.bind(null,
    /
([\s\S]*)?<\/div>\s*<(footer|\/article)>/);
<commit_msg>octopress filters should inherit from jekyll<commit_after>/**
 * Octopress-compatible filters.
 */

var path = require('path');

var base = require('./base');
var _ = require('lodash');

_.extend(exports, require('./jekyll'));

/**
 * Allow non-camelcase naming:
 */
/*jscs:disable requireCamelCaseOrUpperCaseIdentifiers*/

exports.condense_spaces = base.condenseSpaces;

exports.shorthand_url = base.extract.bind(null, /https?:\/\/(\S+)/);
exports.strip_slash = base.extract.bind(null, /(.+|^)\/$/);
exports.titlecase = base.titlecase;

exports.category_links = function(categories) {
  var output = '';

  if (categories) {
    if (!Array.isArray(categories)) {
      categories = [categories];
    }

    /**
     * Sort the list of categories, create a link for each one, and then
     * create a comma-separated list of all the links:
     */

    output = _.sortBy(categories)
      .map(exports.category_link)
      .join(', ');
  }
  return output;
};

exports.category_link = function(category) {
  var dir = exports.options.categoryDir || '';
  var url = path.join(dir, encodeURIComponent(category));

  return '' + category + '';
};

exports.expand_urls = function(s, url) {
  return base.replace(
    '(\\s+(href|src)\\s*=\\s*["|\']{1})(/[^/>]{1}[^"\'>]*)',
    '$1' + ((!url || url === '') ? '/' : url) + '$3',
    s
  );
};

exports.raw_content = base.extract.bind(null,
    /
([\s\S]*)?<\/div>\s*<(footer|\/article)>/);
<|endoftext|>


<commit_before><commit_msg>Fix screenshots of reply threads<commit_after><|endoftext|>


<commit_before>// Background script for loading the Lytspel dictionary and converting words from tradspell
// to Lytspel.

'use strict';  // strict mode

// --- Globals ---
const DICT_FILE = 'data/lytspel-dict.csv';
const dict = new Map();  // dicts will be filled later
const mixedDict = new Map();

// --- Entry point ---
loadDict();
if (!testMode()) {
  browser.runtime.onMessage.addListener(convertWordset);
}

// Trigger loading the dictionary.
function loadDict() {
  if (testMode()) {
    let fs = require('fs');
    fs.readFile('../' + DICT_FILE, 'utf8', (err, data) => {
      if (err) throw err;
      fillDicts(data);
      selfTest();
    });
    return;
  }

  const xmlhttp = new XMLHttpRequest();
  xmlhttp.responseType = 'text';
  xmlhttp.open('GET', browser.extension.getURL(DICT_FILE));

  xmlhttp.onreadystatechange = () => {
    if (xmlhttp.readyState === 4 && (xmlhttp.status === 200 || xmlhttp.status == 0)) {
      fillDicts(xmlhttp.responseText);
    }
  }
  xmlhttp.send();
}

// Convert all known words in wordset to Lytspel, returning a mapping from tradspell to
// Lytspel. Unknown words will be omitted from the returned mapping.
function convertWordset(wordset, sender, sendResponse) {
  const result = {};

  for (const word of wordset) {
    const conv = lookup(word);
    // TODO Also handle genitives etc.
    // Add POS tagging and handle tags both when filling the dict (fillDicts) and here.
    if (conv !== undefined) result[word] = conv;
  }

  sendResponse(result);
}

// --- Callbacks ---

// Fill the dictionary globals from the contents of the CSV file defining the Lytspel dictionary.
function fillDicts(contents) {
  const lines = contents.split(/\r?\n/);
  lines.shift();  // skip header line

  for (const line of lines) {
    const fields = line.split(',');
    const tradspell = fields[0];
    const lytspel = fields[3];

    if (tradspell && lytspel) {
      dict.set(tradspell.toLowerCase(), lytspel.toLowerCase());
      if (isMixedCase(lytspel)) {
        mixedDict.set(tradspell, lytspel);
      }
    }
    // TODO Add support for POS tag (1) and Redirects (2)
  }
}

// --- Primary helpers and callbacks ---

// A word is assumed to be MiXed case if it starts with an upper-case letter and if it
// contains at least one other upper-case and one lower-case letter.
// Words must be ASCII-ified for this function to work correctly.
function isMixedCase(word) {
  return /^[A-Z].*[a-z]/.test(word) && /.[A-Z]/.test(word);
}

// Lookup a word in the dictionary and returned its converted form, restoring the case
// (lower, Capitalized, or ALL_CAPS). MixedCase is also restored provided that *both* the input
// word and the dictionary entry use this case form (e.g. JavaScript -> JaavaScript).
function lookup(word) {
  word = asciiify(word);
  let genitiveS = '';

  if (/'s$/i.test(word)) {
    // Strip final 's (genitive or contraction) and remember for later (handling case)
    genitiveS = word.slice(-2);
    word = word.slice(0, -2);
  }

  let result = dict.get(word.toLowerCase());
  if (result == undefined) return result;

  if (/^[A-Z']+$/.test(word)) {
    result = result.toUpperCase();  // ALL_CAPS
  } else if (/^[A-Z]/.test(word)) {
    if (mixedDict.has(word)) {
      result = mixedDict.get(word); // MixedCase
    } else {
      result = result[0].toUpperCase() + result.slice(1);  // Capitalized
    }
  }

  if (genitiveS) {
    result += genitiveS;
  }

  result = result.split("'").join('’'); // replace normal by typographic apostrophe
  return result;
}

// --- Secondary helpers ---

// Convert a word to its ASCII equivalent. Typographic apostrophes are replaced by
// normal ones and diacritical letters are replaced by their closest ASCII equivalents.
function asciiify(word) {
  let result = word.split('’').join("'"); // ASCII-ify apostrophes

  if (! /^[a-z']*$/i.test(result) ) { // non-ASCII word
    let letters = Array.from(result);

    for (let i = 0; i < letters.length; i++) {
      const letter = letters[i];
      if (letter.codePointAt(0) < 128) continue; // ASCII, nothing to do
      const lowerLetter = letter.toLowerCase();
      let replacement;

      // vowels and semivowels
      if ('áàăâǎåäãąāả'.includes(lowerLetter)) {
        replacement = 'a';
      } else if ('æ'.includes(lowerLetter)) {
        replacement = 'ae';
      } else if ('éèĕêěëẽęēẻ'.includes(lowerLetter)) {
        replacement = 'e';
      } else if ('íìĭîǐïĩįī'.includes(lowerLetter)) {
        replacement = 'i';
      } else if ('óòŏôǒöõøǫōỏ'.includes(lowerLetter)) {
        replacement = 'o';
      } else if ('œ'.includes(lowerLetter)) {
        replacement = 'oe';
      } else if ('úùŭûǔůüũųūủ'.includes(lowerLetter)) {
        replacement = 'u';
      } else if ('ýỳŷẙÿỹȳỷ'.includes(lowerLetter)) {
        replacement = 'y';
      // consonants
      } else if ('ćĉčċç'.includes(lowerLetter)) {
        replacement = 'c';
      } else if ('ďđ'.includes(lowerLetter)) {
        replacement = 'd';
      } else if ('ǵğĝǧġḡ'.includes(lowerLetter)) {
        replacement = 'g';
      } else if ('ĥȟḧħ'.includes(lowerLetter)) {
        replacement = 'h';
      } else if ('ĵǰ'.includes(lowerLetter)) {
        replacement = 'j';
      } else if ('ḱǩ'.includes(lowerLetter)) {
        replacement = 'k';
      } else if ('ĺľłḹ'.includes(lowerLetter)) {
        replacement = 'l';
      } else if ('ḿ'.includes(lowerLetter)) {
        replacement = 'm';
      } else if ('ńǹňñ'.includes(lowerLetter)) {
        replacement = 'n';
      } else if ('ṕ'.includes(lowerLetter)) {
        replacement = 'p';
      } else if ('ŕř'.includes(lowerLetter)) {
        replacement = 'r';
      } else if ('śŝšş'.includes(lowerLetter)) {
        replacement = 's';
      } else if ('ß'.includes(lowerLetter)) {
        replacement = 'ss';
      } else if ('ťẗ'.includes(lowerLetter)) {
        replacement = 't';
      } else if ('ṽ'.includes(lowerLetter)) {
        replacement = 'v';
      } else if ('ẃẁẘẅ'.includes(lowerLetter)) {
        replacement = 'w';
      } else if ('ẍ'.includes(lowerLetter)) {
        replacement = 'x';
      } else if ('źẑž'.includes(lowerLetter)) {
        replacement = 'z';
      }

      if (replacement) {
        if (letter !== lowerLetter) { // original was upper case
          replacement = replacement.toUpperCase();
        }
        letters[i] = replacement;
      }
    }

    result = letters.join('');
  }

  return result;
}

// --- Test code ---

// Check whether this code is running in test mode (without a browser object).
function testMode() {
  return typeof browser === 'undefined';
}

// Run some tests.
function selfTest() {
  console.log('Running self-test...');
  let assert = require('assert');
  testSimpleLookup(assert);
  testCaseIsRestored(assert);
  testApostrophes(assert);
  testGenitiveS(assert);
  testDiacritics(assert);
  console.log('Self-test passed');
}

function testSimpleLookup(assert) {
  assert.equal(lookup('blackbird'), 'blakburd');
  assert.equal(lookup('earthward'), 'urthwerd');
  assert.equal(lookup('shadows'), 'shadoas');
  assert.equal(lookup('sharp'), 'sharp');
  assert.equal(lookup('technology'), 'tec’noleji');
  assert.equal(lookup('tahiti'), 'ta’heeti');   // capitalized in dict
  assert.equal(lookup('notaword'), undefined);
}

// Case should be restored
function testCaseIsRestored(assert) {
  assert.equal(lookup('Score'), 'Scoar');
  assert.equal(lookup('NETWORK'), 'NETWURK');
  assert.equal(lookup('POLITICAL'), 'PO’LITICL');
  assert.equal(lookup('JavaScript'), 'JaavaScript');
  assert.equal(lookup('McCain'), 'McCain');
  assert.equal(lookup('MeV'), 'MeV');
  assert.equal(lookup('ODed'), 'ODed');
  assert.equal(lookup("O'Donnell"), 'O’Donel');
  assert.equal(lookup('PCs'), 'PCs');
  assert.equal(lookup('PowerPC'), 'PowerPC');
}

// Both normal and typographic apostrophes should be accepted
function testApostrophes(assert) {
  assert.equal(lookup("I'm"), 'Y’m');
  assert.equal(lookup('I’m'), 'Y’m');
  assert.equal(lookup("it'll"), 'it’l');
  assert.equal(lookup('it’ll'), 'it’l');
  assert.equal(lookup("O'Connell"), 'O’Conel');
  assert.equal(lookup('O’Connell'), 'O’Conel');
}

// 's (genitive or contraction) is handled correctly (normal and typographic forms)
function testGenitiveS(assert) {
  assert.equal(lookup("He's"), 'Hi’s');
  assert.equal(lookup("boyfriend's"), 'boyfrend’s');
  assert.equal(lookup('He’s'), 'Hi’s');
  assert.equal(lookup('boyfriend’s'), 'boyfrend’s');
  // Upper case words too
  assert.equal(lookup("HE's"), 'HI’s');
  assert.equal(lookup('He’s'), 'Hi’s');
}

function testDiacritics(assert) {
  assert.equal(lookup('café'), 'ca’fay');
  assert.equal(lookup('continuüm'), 'con’tiniuam');
  assert.equal(lookup('doppelgänger'), 'dopelganger');
  assert.equal(lookup('élite'), 'i’leet');
  assert.equal(lookup('Élite'), 'I’leet');
  assert.equal(lookup('épée'), 'ai’pay');
  assert.equal(lookup('ÉPÉE'), 'AI’PAY');
  assert.equal(lookup('hôtel'), 'hoa’tel');
  assert.equal(lookup('Hôtel'), 'Hoa’tel');
  assert.equal(lookup('mañana'), 'maa’nyaanaa');
  assert.equal(lookup('naïve'), 'naa’eev');
  assert.equal(lookup('œuvre'), 'uuvra');
  assert.equal(lookup('Œuvre'), 'Uuvra');
  assert.equal(lookup('tōfu'), 'toafu');
}

//assert.equal(lookup(''), '');
// TODO Handle and test here and in other file:
// * Heuristic for language detection: don't convertText unless at least half of its
//   words are known.
// * Skip script+style tags; also convert alt+title attributes; ensure that no problems
//   remain on e.g.spiegel.de, focus.de
// * POS tags -- test using https://en.wikipedia.org/wiki/Heteronym_(linguistics)#Examples
// * redirects
// * Special case: I and I'm should only remain capitalized at the beginning of sentences
// * Modify Makefile to also update extensions/firefox/data whenever lytspel-dict.csv has changed
// * Recognize URLs in text and treat as non-words (sample: IRC logs)
// * Use ESLint (node package) to check the coding style
// * Add button to turn conversion off and on
// * If called in test mode with argument, convert txt/html/epub file, storing the original
//   as FILE-orig.EXT
// * Also rewrite parts of a page loaded later (e.g. due to scrolling; sample: Youtube
//   comments); possibly use a custom data-\* attribute; can be read and written as
//   elem.dataset.NAME, e.g. elem.dataset.lytspel = conv|seen
// * Add form that allows the conversion of local txt/html/epub files (different interface
//   for same functionality)
// * Add a dialog for converting words and sentences to Lytspel
// * Package extension (see NOTES), tell Matthias, and upload to plugin directory
<commit_msg>Adjust TODOs<commit_after>// Background script for loading the Lytspel dictionary and converting words from tradspell
// to Lytspel.

'use strict';  // strict mode

// --- Globals ---
const DICT_FILE = 'data/lytspel-dict.csv';
const dict = new Map();  // dicts will be filled later
const mixedDict = new Map();

// --- Entry point ---
loadDict();
if (!testMode()) {
  browser.runtime.onMessage.addListener(convertWordset);
}

// Trigger loading the dictionary.
function loadDict() {
  if (testMode()) {
    let fs = require('fs');
    fs.readFile('../' + DICT_FILE, 'utf8', (err, data) => {
      if (err) throw err;
      fillDicts(data);
      selfTest();
    });
    return;
  }

  const xmlhttp = new XMLHttpRequest();
  xmlhttp.responseType = 'text';
  xmlhttp.open('GET', browser.extension.getURL(DICT_FILE));

  xmlhttp.onreadystatechange = () => {
    if (xmlhttp.readyState === 4 && (xmlhttp.status === 200 || xmlhttp.status == 0)) {
      fillDicts(xmlhttp.responseText);
    }
  }
  xmlhttp.send();
}

// Convert all known words in wordset to Lytspel, returning a mapping from tradspell to
// Lytspel. Unknown words will be omitted from the returned mapping.
function convertWordset(wordset, sender, sendResponse) {
  const result = {};

  for (const word of wordset) {
    const conv = lookup(word);
    // TODO Also handle genitives etc.
    // Add POS tagging and handle tags both when filling the dict (fillDicts) and here.
    if (conv !== undefined) result[word] = conv;
  }

  sendResponse(result);
}

// --- Callbacks ---

// Fill the dictionary globals from the contents of the CSV file defining the Lytspel dictionary.
function fillDicts(contents) {
  const lines = contents.split(/\r?\n/);
  lines.shift();  // skip header line

  for (const line of lines) {
    const fields = line.split(',');
    const tradspell = fields[0];
    const lytspel = fields[3];

    if (tradspell && lytspel) {
      dict.set(tradspell.toLowerCase(), lytspel.toLowerCase());
      if (isMixedCase(lytspel)) {
        mixedDict.set(tradspell, lytspel);
      }
    }
    // TODO Add support for POS tag (1) and Redirects (2)
  }
}

// --- Primary helpers and callbacks ---

// A word is assumed to be MiXed case if it starts with an upper-case letter and if it
// contains at least one other upper-case and one lower-case letter.
// Words must be ASCII-ified for this function to work correctly.
function isMixedCase(word) {
  return /^[A-Z].*[a-z]/.test(word) && /.[A-Z]/.test(word);
}

// Lookup a word in the dictionary and returned its converted form, restoring the case
// (lower, Capitalized, or ALL_CAPS). MixedCase is also restored provided that *both* the input
// word and the dictionary entry use this case form (e.g. JavaScript -> JaavaScript).
function lookup(word) {
  word = asciiify(word);
  let genitiveS = '';

  if (/'s$/i.test(word)) {
    // Strip final 's (genitive or contraction) and remember for later (handling case)
    genitiveS = word.slice(-2);
    word = word.slice(0, -2);
  }

  let result = dict.get(word.toLowerCase());
  if (result == undefined) return result;

  if (/^[A-Z']+$/.test(word)) {
    result = result.toUpperCase();  // ALL_CAPS
  } else if (/^[A-Z]/.test(word)) {
    if (mixedDict.has(word)) {
      result = mixedDict.get(word); // MixedCase
    } else {
      result = result[0].toUpperCase() + result.slice(1);  // Capitalized
    }
  }

  if (genitiveS) {
    result += genitiveS;
  }

  result = result.split("'").join('’'); // replace normal by typographic apostrophe
  return result;
}

// --- Secondary helpers ---

// Convert a word to its ASCII equivalent. Typographic apostrophes are replaced by
// normal ones and diacritical letters are replaced by their closest ASCII equivalents.
function asciiify(word) {
  let result = word.split('’').join("'"); // ASCII-ify apostrophes

  if (! /^[a-z']*$/i.test(result) ) { // non-ASCII word
    let letters = Array.from(result);

    for (let i = 0; i < letters.length; i++) {
      const letter = letters[i];
      if (letter.codePointAt(0) < 128) continue; // ASCII, nothing to do
      const lowerLetter = letter.toLowerCase();
      let replacement;

      // vowels and semivowels
      if ('áàăâǎåäãąāả'.includes(lowerLetter)) {
        replacement = 'a';
      } else if ('æ'.includes(lowerLetter)) {
        replacement = 'ae';
      } else if ('éèĕêěëẽęēẻ'.includes(lowerLetter)) {
        replacement = 'e';
      } else if ('íìĭîǐïĩįī'.includes(lowerLetter)) {
        replacement = 'i';
      } else if ('óòŏôǒöõøǫōỏ'.includes(lowerLetter)) {
        replacement = 'o';
      } else if ('œ'.includes(lowerLetter)) {
        replacement = 'oe';
      } else if ('úùŭûǔůüũųūủ'.includes(lowerLetter)) {
        replacement = 'u';
      } else if ('ýỳŷẙÿỹȳỷ'.includes(lowerLetter)) {
        replacement = 'y';
      // consonants
      } else if ('ćĉčċç'.includes(lowerLetter)) {
        replacement = 'c';
      } else if ('ďđ'.includes(lowerLetter)) {
        replacement = 'd';
      } else if ('ǵğĝǧġḡ'.includes(lowerLetter)) {
        replacement = 'g';
      } else if ('ĥȟḧħ'.includes(lowerLetter)) {
        replacement = 'h';
      } else if ('ĵǰ'.includes(lowerLetter)) {
        replacement = 'j';
      } else if ('ḱǩ'.includes(lowerLetter)) {
        replacement = 'k';
      } else if ('ĺľłḹ'.includes(lowerLetter)) {
        replacement = 'l';
      } else if ('ḿ'.includes(lowerLetter)) {
        replacement = 'm';
      } else if ('ńǹňñ'.includes(lowerLetter)) {
        replacement = 'n';
      } else if ('ṕ'.includes(lowerLetter)) {
        replacement = 'p';
      } else if ('ŕř'.includes(lowerLetter)) {
        replacement = 'r';
      } else if ('śŝšş'.includes(lowerLetter)) {
        replacement = 's';
      } else if ('ß'.includes(lowerLetter)) {
        replacement = 'ss';
      } else if ('ťẗ'.includes(lowerLetter)) {
        replacement = 't';
      } else if ('ṽ'.includes(lowerLetter)) {
        replacement = 'v';
      } else if ('ẃẁẘẅ'.includes(lowerLetter)) {
        replacement = 'w';
      } else if ('ẍ'.includes(lowerLetter)) {
        replacement = 'x';
      } else if ('źẑž'.includes(lowerLetter)) {
        replacement = 'z';
      }

      if (replacement) {
        if (letter !== lowerLetter) { // original was upper case
          replacement = replacement.toUpperCase();
        }
        letters[i] = replacement;
      }
    }

    result = letters.join('');
  }

  return result;
}

// --- Test code ---

// Check whether this code is running in test mode (without a browser object).
function testMode() {
  return typeof browser === 'undefined';
}

// Run some tests.
function selfTest() {
  console.log('Running self-test...');
  let assert = require('assert');
  testSimpleLookup(assert);
  testCaseIsRestored(assert);
  testApostrophes(assert);
  testGenitiveS(assert);
  testDiacritics(assert);
  console.log('Self-test passed');
}

function testSimpleLookup(assert) {
  assert.equal(lookup('blackbird'), 'blakburd');
  assert.equal(lookup('earthward'), 'urthwerd');
  assert.equal(lookup('shadows'), 'shadoas');
  assert.equal(lookup('sharp'), 'sharp');
  assert.equal(lookup('technology'), 'tec’noleji');
  assert.equal(lookup('tahiti'), 'ta’heeti');   // capitalized in dict
  assert.equal(lookup('notaword'), undefined);
}

// Case should be restored
function testCaseIsRestored(assert) {
  assert.equal(lookup('Score'), 'Scoar');
  assert.equal(lookup('NETWORK'), 'NETWURK');
  assert.equal(lookup('POLITICAL'), 'PO’LITICL');
  assert.equal(lookup('JavaScript'), 'JaavaScript');
  assert.equal(lookup('McCain'), 'McCain');
  assert.equal(lookup('MeV'), 'MeV');
  assert.equal(lookup('ODed'), 'ODed');
  assert.equal(lookup("O'Donnell"), 'O’Donel');
  assert.equal(lookup('PCs'), 'PCs');
  assert.equal(lookup('PowerPC'), 'PowerPC');
}

// Both normal and typographic apostrophes should be accepted
function testApostrophes(assert) {
  assert.equal(lookup("I'm"), 'Y’m');
  assert.equal(lookup('I’m'), 'Y’m');
  assert.equal(lookup("it'll"), 'it’l');
  assert.equal(lookup('it’ll'), 'it’l');
  assert.equal(lookup("O'Connell"), 'O’Conel');
  assert.equal(lookup('O’Connell'), 'O’Conel');
}

// 's (genitive or contraction) is handled correctly (normal and typographic forms)
function testGenitiveS(assert) {
  assert.equal(lookup("He's"), 'Hi’s');
  assert.equal(lookup("boyfriend's"), 'boyfrend’s');
  assert.equal(lookup('He’s'), 'Hi’s');
  assert.equal(lookup('boyfriend’s'), 'boyfrend’s');
  // Upper case words too
  assert.equal(lookup("HE's"), 'HI’s');
  assert.equal(lookup('He’s'), 'Hi’s');
}

function testDiacritics(assert) {
  assert.equal(lookup('café'), 'ca’fay');
  assert.equal(lookup('continuüm'), 'con’tiniuam');
  assert.equal(lookup('doppelgänger'), 'dopelganger');
  assert.equal(lookup('élite'), 'i’leet');
  assert.equal(lookup('Élite'), 'I’leet');
  assert.equal(lookup('épée'), 'ai’pay');
  assert.equal(lookup('ÉPÉE'), 'AI’PAY');
  assert.equal(lookup('hôtel'), 'hoa’tel');
  assert.equal(lookup('Hôtel'), 'Hoa’tel');
  assert.equal(lookup('mañana'), 'maa’nyaanaa');
  assert.equal(lookup('naïve'), 'naa’eev');
  assert.equal(lookup('œuvre'), 'uuvra');
  assert.equal(lookup('Œuvre'), 'Uuvra');
  assert.equal(lookup('tōfu'), 'toafu');
}

//assert.equal(lookup(''), '');
// TODO Handle and test here and in other file:
// * Skip script+style tags (cf. elem.tagName /i); ensure that no problems
//   remain on e.g. spiegel.de, focus.de
// * POS tags -- test using https://en.wikipedia.org/wiki/Heteronym_(linguistics)#Examples
// * redirects
// * Special case: I and I'm should only remain capitalized at the beginning of sentences
// * Modify Makefile to also update extensions/firefox/data whenever lytspel-dict.csv has changed
// * Recognize URLs in text and treat as non-words (sample: IRC logs)
// * Use ESLint (node package) to check the coding style
// * Add button to turn conversion off and on
// * If called in test mode with argument, convert txt/html/epub file, storing the original
//   as FILE-orig.EXT
// * Also rewrite parts of a page loaded later (e.g. due to scrolling; sample: Youtube
//   comments); possibly use a custom data-\* attribute; can be read and written as
//   elem.dataset.NAME, e.g. elem.dataset.lytspel = conv|seen
// * Add form that allows the conversion of local txt/html/epub files (different interface
//   for same functionality)
// * Add a dialog for converting words and sentences to Lytspel
// * Package extension (see NOTES), tell Matthias, and upload to plugin directory
<|endoftext|>


<commit_before>let lab = [];
let anc = [];
let w = 1; // box width
let h = 1; // box width

const maxMove = 25.0;
const maxAngle = 0.5;
let acc = 0;
let rej = 0;

// weights
const wLen = 5.0; // leader line length
const wInter = 50.0; // leader line intersection
const wLabLab = 50.0; // label-label overlap
const wLabOrg = 50.0; // additional label-origin overlap weights (added to `wLabAnc`)
const wLabAnc = 10.0; // overlap between label and other anchors (i.e., annotations)
const wOrient = 0.1; // orientation bias

// booleans for user defined functions
let userEnergy = false;
let userSchedule = false;

let userDefinedEnergy;
let userDefinedSchedule;

let is1dOnly = false;

// returns true if two lines intersect, else false
// from http://paulbourke.net/geometry/lineline2d/
const intersect = (x1, x2, x3, x4, y1, y2, y3, y4) => {
  const denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
  const numera = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3));
  const numerb = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3));

  /* Is the intersection along the the segments */
  const mua = numera / denom;
  const mub = numerb / denom;

  return (!(mua < 0 || mua > 1 || mub < 0 || mub > 1));
};

// energy function, tailored for label placement
const energy = (index) => {
  const m = lab.length;
  const n = anc.length;
  const mn = Math.max(m, n);
  const l = lab[index];
  let ener = 0;
  const dx = l.x - l.ox;
  const dy = l.y - l.oy;
  const dist = Math.sqrt((dx * dx) + (dy * dy));
  const distCenterToBorder = Math.sqrt((l.wh * l.wh) + (l.hh * l.hh));
  let overlap = true;
  // let amount = 0;
  // let theta = 0;

  // penalty for length of leader line
  ener += Math.abs(dist - distCenterToBorder) * wLen;

  // label orientation bias (Fritz: ignored for now)
  // dx /= dist;
  // dy /= dist;
  // if (dx > 0 && dy > 0) ener += 0 * wOrient;
  // else if (dx < 0 && dy > 0) ener += 1 * wOrient;
  // else if (dx < 0 && dy < 0) ener += 2 * wOrient;
  // else ener += 3 * wOrient;

  const x21 = l.x - l.wh - 1;
  const y21 = l.y - l.hh - 1;
  const x22 = l.x + l.wh + 1;
  const y22 = l.y + l.hh + 1;
  let x11;
  let x12;
  let y11;
  let y12;
  let xOverlap;
  let yOverlap;
  let overlapArea;

  // For every annotation or label
  // Note: we know that the first m anchors are the label origins
  for (let i = 0; i < mn; i++) {
    // For every other label (m = number of labels)
    if (i !== index && i < m) {
      const otherLabel = lab[i];
      // Test if leader lines intersect...
      overlap = intersect(
        anc[index].x,
        l.x,
        anc[i].x,
        otherLabel.x,
        anc[index].y,
        l.y,
        anc[i].y,
        otherLabel.y
      );

      // ...and add a penalty if they do
      if (overlap) ener += wInter;

      // Penalty for label-label overlap
      x11 = otherLabel.x - otherLabel.wh - 1;
      y11 = otherLabel.y - otherLabel.hh - 1;
      x12 = otherLabel.x + otherLabel.wh + 1;
      y12 = otherLabel.y + otherLabel.hh + 1;
      xOverlap = Math.max(0, Math.min(x12, x22) - Math.max(x11, x21));
      yOverlap = Math.max(0, Math.min(y12, y22) - Math.max(y11, y21));
      overlapArea = xOverlap * yOverlap;
      ener += (overlapArea * wLabLab);
    }

    // penalty for label-anchor overlap
    x11 = anc[i].x - anc[i].wh - 1;
    y11 = anc[i].y - anc[i].hh - 1;
    x12 = anc[i].x + anc[i].wh + 1;
    y12 = anc[i].y + anc[i].hh + 1;
    xOverlap = Math.max(0, Math.min(x12, x22) - Math.max(x11, x21));
    yOverlap = Math.max(0, Math.min(y12, y22) - Math.max(y11, y21));
    overlapArea = xOverlap * yOverlap;
    ener += overlapArea * (wLabAnc + (wLabOrg * (i < m)));
  }
  return ener;
};

// Monte Carlo translation move
const mcmove = (currT) => {
  // select a random label
  const i = Math.floor(Math.random() * lab.length);
  const l = lab[i];

  // save old coordinates
  const xOld = l.x;
  const yOld = l.y;

  // old energy
  const oldEnergy = userEnergy
    ? userDefinedEnergy(i, lab, anc)
    : energy(i);

  const getRndMove = () => (Math.random() - 0.5) * maxMove * Math.max(0.5, currT);

  // random translation
  const moveX = +(((is1dOnly * !l.isVerticalOnly) + !is1dOnly) && getRndMove());
  const moveY = +(((is1dOnly * l.isVerticalOnly) + !is1dOnly) && getRndMove());
  l.x += moveX;
  l.y += moveY;

  // hard wall boundaries
  // if (l.x > w) l.x = xOld;
  // if (l.x < 0) l.x = xOld;
  // if (l.y > h) l.y = yOld;
  // if (l.y < 0) l.y = yOld;

  // new energy
  const newEnergy = userEnergy
    ? userDefinedEnergy(i, lab, anc)
    : energy(i);

  // delta E
  const deltaEnergy = newEnergy - oldEnergy;

  if (Math.random() < Math.exp(-deltaEnergy / currT)) {
    acc += 1;
  } else {
    // move back to old coordinates
    l.x = xOld;
    l.y = yOld;
    rej += 1;
  }
};

// Monte Carlo rotation move
const mcrotate = (currT) => {
  // select a random label
  const i = Math.floor(Math.random() * lab.length);

  // save old coordinates
  const xOld = lab[i].x;
  const yOld = lab[i].y;

  // old energy
  const oldEnergy = userEnergy
    ? userDefinedEnergy(i, lab, anc)
    : energy(i);

  // random angle
  const angle = (Math.random() - 0.5) * maxAngle;

  const s = Math.sin(angle);
  const c = Math.cos(angle);

  // translate label (relative to anchor at origin):
  lab[i].x -= anc[i].x;
  lab[i].y -= anc[i].y;

  // rotate label
  const xNew = (lab[i].x * c) - (lab[i].y * s);
  const yNew = (lab[i].x * s) + (lab[i].y * c);

  // translate label back
  lab[i].x = xNew + anc[i].x;
  lab[i].y = yNew + anc[i].y;

  // hard wall boundaries
  if (lab[i].x > w) lab[i].x = xOld;
  if (lab[i].x < 0) lab[i].x = xOld;
  if (lab[i].y > h) lab[i].y = yOld;
  if (lab[i].y < 0) lab[i].y = yOld;

  // new energy
  const newEnergy = userEnergy
    ? userDefinedEnergy(i, lab, anc)
    : energy(i);

  // delta E
  const deltaEnergy = newEnergy - oldEnergy;

  if (Math.random() < Math.exp(-deltaEnergy / currT)) {
    acc += 1;
  } else {
    // move back to old coordinates
    lab[i].x = xOld;
    lab[i].y = yOld;
    rej += 1;
  }
};

// linear cooling
const coolingSchedule = (currT, initialT, nsweeps) => (currT - (initialT / nsweeps));

const labeler = {};

// main simulated annealing function
labeler.start = (nsweeps, t = 1.0) => {
  const m = lab.length;
  const initialT = t;
  let currT = initialT;

  for (let i = 0; i < nsweeps; i++) {
    for (let j = 0; j < m; j++) {
      mcmove(currT);
      // Fritz: ignoring rotations for now
      // if (Math.random() < 0.5) mcmove(currT);
      // else mcrotate(currT);
    }
    currT = coolingSchedule(currT, initialT, nsweeps);
  }
};

labeler.width = (x) => {
// users insert graph width
  if (!arguments.length) return w;
  w = x;
  return labeler;
};

labeler.height = (x) => {
// users insert graph height
  if (!arguments.length) return h;
  h = x;
  return labeler;
};

// users insert label positions
labeler.label = (x) => {
  if (!arguments.length) return lab;
  lab = x;
  return labeler;
};

// users insert anchor positions
labeler.anchor = (x) => {
  if (!arguments.length) return anc;
  anc = x;
  return labeler;
};

labeler.altEnergy = (x) => {
// user defined energy
  if (!arguments.length) return energy;
  userDefinedEnergy = x;
  userEnergy = true;
  return labeler;
};

labeler.altSchedule = (x) => {
// user defined coolingSchedule
  if (!arguments.length) return coolingSchedule;
  userDefinedSchedule = x;
  userSchedule = true;
  return labeler;
};

/**
 * Restrict annealing to be one dimensional if `isFalse` is `false`.
 * @param   {boolean}  isFalse  If `true` deactivate one-dimensional annealing.
 * @return  {object}  Return the labeler for chaining.
 */
labeler.is1dOnly = (isFalse = false) => {
  if (!arguments.length) return is1dOnly;
  is1dOnly = !isFalse;
  return labeler;
};

export default labeler;
<commit_msg>Make sure optional values are converted to boolean before using in math<commit_after>let lab = [];
let anc = [];
let w = 1; // box width
let h = 1; // box width

const maxMove = 25.0;
const maxAngle = 0.5;
let acc = 0;
let rej = 0;

// weights
const wLen = 5.0; // leader line length
const wInter = 50.0; // leader line intersection
const wLabLab = 50.0; // label-label overlap
const wLabOrg = 50.0; // additional label-origin overlap weights (added to `wLabAnc`)
const wLabAnc = 10.0; // overlap between label and other anchors (i.e., annotations)
const wOrient = 0.1; // orientation bias

// booleans for user defined functions
let userEnergy = false;
let userSchedule = false;

let userDefinedEnergy;
let userDefinedSchedule;

let is1dOnly = false;

// returns true if two lines intersect, else false
// from http://paulbourke.net/geometry/lineline2d/
const intersect = (x1, x2, x3, x4, y1, y2, y3, y4) => {
  const denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
  const numera = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3));
  const numerb = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3));

  /* Is the intersection along the the segments */
  const mua = numera / denom;
  const mub = numerb / denom;

  return (!(mua < 0 || mua > 1 || mub < 0 || mub > 1));
};

// energy function, tailored for label placement
const energy = (index) => {
  const m = lab.length;
  const n = anc.length;
  const mn = Math.max(m, n);
  const l = lab[index];
  let ener = 0;
  const dx = l.x - l.ox;
  const dy = l.y - l.oy;
  const dist = Math.sqrt((dx * dx) + (dy * dy));
  const distCenterToBorder = Math.sqrt((l.wh * l.wh) + (l.hh * l.hh));
  let overlap = true;
  // let amount = 0;
  // let theta = 0;

  // penalty for length of leader line
  ener += Math.abs(dist - distCenterToBorder) * wLen;

  // label orientation bias (Fritz: ignored for now)
  // dx /= dist;
  // dy /= dist;
  // if (dx > 0 && dy > 0) ener += 0 * wOrient;
  // else if (dx < 0 && dy > 0) ener += 1 * wOrient;
  // else if (dx < 0 && dy < 0) ener += 2 * wOrient;
  // else ener += 3 * wOrient;

  const x21 = l.x - l.wh - 1;
  const y21 = l.y - l.hh - 1;
  const x22 = l.x + l.wh + 1;
  const y22 = l.y + l.hh + 1;
  let x11;
  let x12;
  let y11;
  let y12;
  let xOverlap;
  let yOverlap;
  let overlapArea;

  // For every annotation or label
  // Note: we know that the first m anchors are the label origins
  for (let i = 0; i < mn; i++) {
    // For every other label (m = number of labels)
    if (i !== index && i < m) {
      const otherLabel = lab[i];
      // Test if leader lines intersect...
      overlap = intersect(
        anc[index].x,
        l.x,
        anc[i].x,
        otherLabel.x,
        anc[index].y,
        l.y,
        anc[i].y,
        otherLabel.y
      );

      // ...and add a penalty if they do
      if (overlap) ener += wInter;

      // Penalty for label-label overlap
      x11 = otherLabel.x - otherLabel.wh - 1;
      y11 = otherLabel.y - otherLabel.hh - 1;
      x12 = otherLabel.x + otherLabel.wh + 1;
      y12 = otherLabel.y + otherLabel.hh + 1;
      xOverlap = Math.max(0, Math.min(x12, x22) - Math.max(x11, x21));
      yOverlap = Math.max(0, Math.min(y12, y22) - Math.max(y11, y21));
      overlapArea = xOverlap * yOverlap;
      ener += (overlapArea * wLabLab);
    }

    // penalty for label-anchor overlap
    x11 = anc[i].x - anc[i].wh - 1;
    y11 = anc[i].y - anc[i].hh - 1;
    x12 = anc[i].x + anc[i].wh + 1;
    y12 = anc[i].y + anc[i].hh + 1;
    xOverlap = Math.max(0, Math.min(x12, x22) - Math.max(x11, x21));
    yOverlap = Math.max(0, Math.min(y12, y22) - Math.max(y11, y21));
    overlapArea = xOverlap * yOverlap;
    ener += overlapArea * (wLabAnc + (wLabOrg * (i < m)));
  }
  return ener;
};

// Monte Carlo translation move
const mcmove = (currT) => {
  // select a random label
  const i = Math.floor(Math.random() * lab.length);
  const l = lab[i];

  // save old coordinates
  const xOld = l.x;
  const yOld = l.y;

  // old energy
  const oldEnergy = userEnergy
    ? userDefinedEnergy(i, lab, anc)
    : energy(i);

  const getRndMove = () => (Math.random() - 0.5) * maxMove * Math.max(0.5, currT);

  // random translation
  const moveX = +(((is1dOnly * !l.isVerticalOnly) + !is1dOnly) && getRndMove());
  const moveY = +(((is1dOnly * !!l.isVerticalOnly) + !is1dOnly) && getRndMove());
  l.x += moveX;
  l.y += moveY;

  // hard wall boundaries
  // if (l.x > w) l.x = xOld;
  // if (l.x < 0) l.x = xOld;
  // if (l.y > h) l.y = yOld;
  // if (l.y < 0) l.y = yOld;

  // new energy
  const newEnergy = userEnergy
    ? userDefinedEnergy(i, lab, anc)
    : energy(i);

  // delta E
  const deltaEnergy = newEnergy - oldEnergy;

  if (Math.random() < Math.exp(-deltaEnergy / currT)) {
    acc += 1;
  } else {
    // move back to old coordinates
    l.x = xOld;
    l.y = yOld;
    rej += 1;
  }
};

// Monte Carlo rotation move
const mcrotate = (currT) => {
  // select a random label
  const i = Math.floor(Math.random() * lab.length);

  // save old coordinates
  const xOld = lab[i].x;
  const yOld = lab[i].y;

  // old energy
  const oldEnergy = userEnergy
    ? userDefinedEnergy(i, lab, anc)
    : energy(i);

  // random angle
  const angle = (Math.random() - 0.5) * maxAngle;

  const s = Math.sin(angle);
  const c = Math.cos(angle);

  // translate label (relative to anchor at origin):
  lab[i].x -= anc[i].x;
  lab[i].y -= anc[i].y;

  // rotate label
  const xNew = (lab[i].x * c) - (lab[i].y * s);
  const yNew = (lab[i].x * s) + (lab[i].y * c);

  // translate label back
  lab[i].x = xNew + anc[i].x;
  lab[i].y = yNew + anc[i].y;

  // hard wall boundaries
  if (lab[i].x > w) lab[i].x = xOld;
  if (lab[i].x < 0) lab[i].x = xOld;
  if (lab[i].y > h) lab[i].y = yOld;
  if (lab[i].y < 0) lab[i].y = yOld;

  // new energy
  const newEnergy = userEnergy
    ? userDefinedEnergy(i, lab, anc)
    : energy(i);

  // delta E
  const deltaEnergy = newEnergy - oldEnergy;

  if (Math.random() < Math.exp(-deltaEnergy / currT)) {
    acc += 1;
  } else {
    // move back to old coordinates
    lab[i].x = xOld;
    lab[i].y = yOld;
    rej += 1;
  }
};

// linear cooling
const coolingSchedule = (currT, initialT, nsweeps) => (currT - (initialT / nsweeps));

const labeler = {};

// main simulated annealing function
labeler.start = (nsweeps, t = 1.0) => {
  const m = lab.length;
  const initialT = t;
  let currT = initialT;

  for (let i = 0; i < nsweeps; i++) {
    for (let j = 0; j < m; j++) {
      mcmove(currT);
      // Fritz: ignoring rotations for now
      // if (Math.random() < 0.5) mcmove(currT);
      // else mcrotate(currT);
    }
    currT = coolingSchedule(currT, initialT, nsweeps);
  }
};

labeler.width = (x) => {
// users insert graph width
  if (!arguments.length) return w;
  w = x;
  return labeler;
};

labeler.height = (x) => {
// users insert graph height
  if (!arguments.length) return h;
  h = x;
  return labeler;
};

// users insert label positions
labeler.label = (x) => {
  if (!arguments.length) return lab;
  lab = x;
  return labeler;
};

// users insert anchor positions
labeler.anchor = (x) => {
  if (!arguments.length) return anc;
  anc = x;
  return labeler;
};

labeler.altEnergy = (x) => {
// user defined energy
  if (!arguments.length) return energy;
  userDefinedEnergy = x;
  userEnergy = true;
  return labeler;
};

labeler.altSchedule = (x) => {
// user defined coolingSchedule
  if (!arguments.length) return coolingSchedule;
  userDefinedSchedule = x;
  userSchedule = true;
  return labeler;
};

/**
 * Restrict annealing to be one dimensional if `isFalse` is `false`.
 * @param   {boolean}  isFalse  If `true` deactivate one-dimensional annealing.
 * @return  {object}  Return the labeler for chaining.
 */
labeler.is1dOnly = (isFalse = false) => {
  if (!arguments.length) return is1dOnly;
  is1dOnly = !isFalse;
  return labeler;
};

export default labeler;
<|endoftext|>