<commit_before>/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var Plotly = require('../../plotly');
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');
var setCursor = require('../../lib/setcursor');
var dragElement = require('../dragelement');

var annotations = module.exports = {};

annotations.ARROWPATHS = require('./arrow_paths');

annotations.layoutAttributes = require('./attributes');

annotations.supplyLayoutDefaults = function(layoutIn, layoutOut) {
    var containerIn = layoutIn.annotations || [],
        containerOut = layoutOut.annotations = [];

    for(var i = 0; i < containerIn.length; i++) {
        containerOut.push(handleAnnotationDefaults(containerIn[i] || {}, layoutOut));
    }
};

function handleAnnotationDefaults(annIn, fullLayout) {
    var annOut = {};

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(annIn, annOut, annotations.layoutAttributes, attr, dflt);
    }

    coerce('opacity');
    coerce('align');
    coerce('bgcolor');
    var borderColor = coerce('bordercolor'),
        borderOpacity = Plotly.Color.opacity(borderColor);
    coerce('borderpad');
    var borderWidth = coerce('borderwidth');
    var showArrow = coerce('showarrow');
    if(showArrow) {
        coerce('arrowcolor',
            borderOpacity ? annOut.bordercolor : Plotly.Color.defaultLine);
        coerce('arrowhead');
        coerce('arrowsize');
        coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);
        coerce('ax');
        coerce('ay');

        // if you have one part of arrow length you should have both
        Plotly.Lib.noneOrAll(annIn, annOut, ['ax', 'ay']);
    }
    coerce('text', showArrow ? '&nbsp;' : 'new text');
    coerce('textangle');
    Plotly.Lib.coerceFont(coerce, 'font', fullLayout.font);

    // positioning
    var axLetters = ['x','y'];
    for(var i = 0; i < 2; i++) {
        var axLetter = axLetters[i],
            tdMock = {_fullLayout: fullLayout};

        // xref, yref
        var axRef = Plotly.Axes.coerceRef(annIn, annOut, tdMock, axLetter);

        // x, y
        var defaultPosition = 0.5;
        if(axRef!=='paper') {
            var ax = Plotly.Axes.getFromId(tdMock, axRef);
            defaultPosition = ax.range[0] + defaultPosition * (ax.range[1] - ax.range[0]);

            // convert date or category strings to numbers
            if(['date','category'].indexOf(ax.type)!==-1 &&
                    typeof annIn[axLetter]==='string') {
                var newval;
                if(ax.type==='date') {
                    newval = Plotly.Lib.dateTime2ms(annIn[axLetter]);
                    if(newval!==false) annIn[axLetter] = newval;
                }
                else if((ax._categories||[]).length) {
                    newval = ax._categories.indexOf(annIn[axLetter]);
                    if(newval!==-1) annIn[axLetter] = newval;
                }
            }
        }
        coerce(axLetter, defaultPosition);

        // xanchor, yanchor
        if(!showArrow) coerce(axLetter + 'anchor');
    }

    // if you have one coordinate you should have both
    Plotly.Lib.noneOrAll(annIn, annOut, ['x', 'y']);

    return annOut;
}

annotations.drawAll = function(gd) {
    var fullLayout = gd._fullLayout;
    fullLayout._infolayer.selectAll('.annotation').remove();
    for(var i = 0; i < fullLayout.annotations.length; i++) {
        annotations.draw(gd, i);
    }
    return Plotly.Plots.previousPromises(gd);
};

annotations.add = function(gd) {
    var nextAnn = gd._fullLayout.annotations.length;
    Plotly.relayout(gd, 'annotations['+nextAnn+']', 'add');
};

// -----------------------------------------------------
// make or edit an annotation on the graph
// -----------------------------------------------------

// annotations are stored in gd.layout.annotations, an array of objects
// index can point to one item in this array,
//  or non-numeric to simply add a new one
//  or -1 to modify all existing
// opt can be the full options object, or one key (to be set to value)
//  or undefined to simply redraw
// if opt is blank, val can be 'add' or a full options object to add a new
//  annotation at that point in the array, or 'remove' to delete this one
annotations.draw = function(gd, index, opt, value) {
    var layout = gd.layout,
        fullLayout = gd._fullLayout,
        i;

    if(!isNumeric(index) || index===-1) {
        // no index provided - we're operating on ALL annotations
        if(!index && Array.isArray(value)) {
            // a whole annotation array is passed in
            // (as in, redo of delete all)
            layout.annotations = value;
            annotations.supplyLayoutDefaults(layout, fullLayout);
            annotations.drawAll(gd);
            return;
        }
        else if(value==='remove') {
            // delete all
            delete layout.annotations;
            fullLayout.annotations = [];
            annotations.drawAll(gd);
            return;
        }
        else if(opt && value!=='add') {
            // make the same change to all annotations
            for(i = 0; i < fullLayout.annotations.length; i++) {
                annotations.draw(gd, i, opt, value);
            }
            return;
        }
        else {
            // add a new empty annotation
            index = fullLayout.annotations.length;
            fullLayout.annotations.push({});
        }
    }

    if(!opt && value) {
        if(value==='remove') {
            fullLayout._infolayer.selectAll('.annotation[data-index="'+index+'"]')
                .remove();
            fullLayout.annotations.splice(index,1);
            layout.annotations.splice(index,1);
            for(i=index; i<fullLayout.annotations.length; i++) {
                fullLayout._infolayer
                    .selectAll('.annotation[data-index="'+(i+1)+'"]')
                    .attr('data-index',String(i));

                // redraw all annotations past the removed one,
                // so they bind to the right events
                annotations.draw(gd,i);
            }
            return;
        }
        else if(value==='add' || Plotly.Lib.isPlainObject(value)) {
            fullLayout.annotations.splice(index,0,{});

            var rule = Plotly.Lib.isPlainObject(value) ?
                    Plotly.Lib.extendFlat({}, value) :
                    {text: 'New text'};

            if(layout.annotations) {
                layout.annotations.splice(index, 0, rule);
            } else {
                layout.annotations = [rule];
            }

            for(i=fullLayout.annotations.length-1; i>index; i--) {
                fullLayout._infolayer
                    .selectAll('.annotation[data-index="'+(i-1)+'"]')
                    .attr('data-index',String(i));
                annotations.draw(gd,i);
            }
        }
    }

    // remove the existing annotation if there is one
    fullLayout._infolayer.selectAll('.annotation[data-index="'+index+'"]').remove();

    // remember a few things about what was already there,
    var optionsIn = layout.annotations[index],
        oldPrivate = fullLayout.annotations[index];

    // not sure how we're getting here... but C12 is seeing a bug
    // where we fail here when they add/remove annotations
    if(!optionsIn) return;

    var oldRef = {xref: optionsIn.xref, yref: optionsIn.yref};

    // alter the input annotation as requested
    var optionsEdit = {};
    if(typeof opt === 'string' && opt) optionsEdit[opt] = value;
    else if(Plotly.Lib.isPlainObject(opt)) optionsEdit = opt;

    var optionKeys = Object.keys(optionsEdit);
    for(i = 0; i < optionKeys.length; i++) {
        var k = optionKeys[i];
        Plotly.Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]);
    }

    var gs = fullLayout._size;

    var axLetters = ['x', 'y'];
    for(i = 0; i < 2; i++) {
        var axLetter = axLetters[i];
        // if we don't have an explicit position already,
        // don't set one just because we're changing references
        // or axis type.
        // the defaults will be consistent most of the time anyway,
        // except in log/linear changes
        if(optionsEdit[axLetter]!==undefined ||
                optionsIn[axLetter]===undefined) {
            continue;
        }

        var axOld = Plotly.Axes.getFromId(gd,
                Plotly.Axes.coerceRef(oldRef, {}, gd, axLetter)),
            axNew = Plotly.Axes.getFromId(gd,
                Plotly.Axes.coerceRef(optionsIn, {}, gd, axLetter)),
            position = optionsIn[axLetter],
            axTypeOld = oldPrivate['_' + axLetter + 'type'];

        if(optionsEdit[axLetter + 'ref']!==undefined) {
            var autoAnchor = optionsIn[axLetter + 'anchor'] === 'auto',
                plotSize = (axLetter === 'x' ? gs.w : gs.h),
                halfSizeFrac = (oldPrivate['_' + axLetter + 'size'] || 0) /
                    (2 * plotSize);
            if(axOld && axNew) { // data -> different data
                // go to the same fraction of the axis length
                // whether or not these axes share a domain

                // first convert to fraction of the axis
                position = (position - axOld.range[0]) /
                    (axOld.range[1] - axOld.range[0]);

                // then convert to new data coordinates at the same fraction
                position = axNew.range[0] +
                    position * (axNew.range[1] - axNew.range[0]);
            }
            else if(axOld) { // data -> paper
                // first convert to fraction of the axis
                position = (position - axOld.range[0]) /
                    (axOld.range[1] - axOld.range[0]);

                // next scale the axis to the whole plot
                position = axOld.domain[0] +
                    position * (axOld.domain[1] - axOld.domain[0]);

                // finally see if we need to adjust auto alignment
                // because auto always means middle / center alignment for data,
                // but it changes for page alignment based on the closest side
                if(autoAnchor) {
                    var posPlus = position + halfSizeFrac,
                        posMinus = position - halfSizeFrac;
                    if(position + posMinus < 2/3) position = posMinus;
                    else if(position + posPlus > 4/3) position = posPlus;
                }
            }
            else if(axNew) { // paper -> data
                // first see if we need to adjust auto alignment
                if(autoAnchor) {
                    if(position < 1/3) position += halfSizeFrac;
                    else if(position > 2/3) position -= halfSizeFrac;
                }

                // next convert to fraction of the axis
                position = (position - axNew.domain[0]) /
                    (axNew.domain[1] - axNew.domain[0]);

                // finally convert to data coordinates
                position = axNew.range[0] +
                    position * (axNew.range[1] - axNew.range[0]);
            }
        }

        if(axNew && axNew===axOld && axTypeOld) {
            if(axTypeOld==='log' && axNew.type!=='log') {
                position = Math.pow(10,position);
            }
            else if(axTypeOld!=='log' && axNew.type==='log') {
                position = (position>0) ?
                    Math.log(position)/Math.LN10 : undefined;
            }
        }

        optionsIn[axLetter] = position;
    }

    var options = handleAnnotationDefaults(optionsIn, fullLayout);
    fullLayout.annotations[index] = options;

    var xa = Plotly.Axes.getFromId(gd, options.xref),
        ya = Plotly.Axes.getFromId(gd, options.yref),
        annPosPx = {x: 0, y: 0},
        textangle = +options.textangle || 0;

    // create the components
    // made a single group to contain all, so opacity can work right
    // with border/arrow together this could handle a whole bunch of
    // cleanup at this point, but works for now
    var anngroup = fullLayout._infolayer.append('g')
        .classed('annotation', true)
        .attr('data-index', String(index))
        .style('opacity', options.opacity)
        .on('click', function() {
            gd._dragging = false;
            gd.emit('plotly_clickannotation', {
                index: index,
                annotation: optionsIn,
                fullAnnotation: options
            });
        });

    // another group for text+background so that they can rotate together
    var anng = anngroup.append('g')
        .classed('annotation-text-g', true)
        .attr('data-index', String(index));

    var ann = anng.append('g')
        .attr('transform', 'translate(0,0)');

    var borderwidth = options.borderwidth,
        borderpad = options.borderpad,
        borderfull = borderwidth + borderpad;

    var annbg = ann.append('rect')
        .attr('class','bg')<commit_msg>Remove unnecessary transform<commit_after>/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var Plotly = require('../../plotly');
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');
var setCursor = require('../../lib/setcursor');
var dragElement = require('../dragelement');

var annotations = module.exports = {};

annotations.ARROWPATHS = require('./arrow_paths');

annotations.layoutAttributes = require('./attributes');

annotations.supplyLayoutDefaults = function(layoutIn, layoutOut) {
    var containerIn = layoutIn.annotations || [],
        containerOut = layoutOut.annotations = [];

    for(var i = 0; i < containerIn.length; i++) {
        containerOut.push(handleAnnotationDefaults(containerIn[i] || {}, layoutOut));
    }
};

function handleAnnotationDefaults(annIn, fullLayout) {
    var annOut = {};

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(annIn, annOut, annotations.layoutAttributes, attr, dflt);
    }

    coerce('opacity');
    coerce('align');
    coerce('bgcolor');
    var borderColor = coerce('bordercolor'),
        borderOpacity = Plotly.Color.opacity(borderColor);
    coerce('borderpad');
    var borderWidth = coerce('borderwidth');
    var showArrow = coerce('showarrow');
    if(showArrow) {
        coerce('arrowcolor',
            borderOpacity ? annOut.bordercolor : Plotly.Color.defaultLine);
        coerce('arrowhead');
        coerce('arrowsize');
        coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);
        coerce('ax');
        coerce('ay');

        // if you have one part of arrow length you should have both
        Plotly.Lib.noneOrAll(annIn, annOut, ['ax', 'ay']);
    }
    coerce('text', showArrow ? '&nbsp;' : 'new text');
    coerce('textangle');
    Plotly.Lib.coerceFont(coerce, 'font', fullLayout.font);

    // positioning
    var axLetters = ['x','y'];
    for(var i = 0; i < 2; i++) {
        var axLetter = axLetters[i],
            tdMock = {_fullLayout: fullLayout};

        // xref, yref
        var axRef = Plotly.Axes.coerceRef(annIn, annOut, tdMock, axLetter);

        // x, y
        var defaultPosition = 0.5;
        if(axRef!=='paper') {
            var ax = Plotly.Axes.getFromId(tdMock, axRef);
            defaultPosition = ax.range[0] + defaultPosition * (ax.range[1] - ax.range[0]);

            // convert date or category strings to numbers
            if(['date','category'].indexOf(ax.type)!==-1 &&
                    typeof annIn[axLetter]==='string') {
                var newval;
                if(ax.type==='date') {
                    newval = Plotly.Lib.dateTime2ms(annIn[axLetter]);
                    if(newval!==false) annIn[axLetter] = newval;
                }
                else if((ax._categories||[]).length) {
                    newval = ax._categories.indexOf(annIn[axLetter]);
                    if(newval!==-1) annIn[axLetter] = newval;
                }
            }
        }
        coerce(axLetter, defaultPosition);

        // xanchor, yanchor
        if(!showArrow) coerce(axLetter + 'anchor');
    }

    // if you have one coordinate you should have both
    Plotly.Lib.noneOrAll(annIn, annOut, ['x', 'y']);

    return annOut;
}

annotations.drawAll = function(gd) {
    var fullLayout = gd._fullLayout;
    fullLayout._infolayer.selectAll('.annotation').remove();
    for(var i = 0; i < fullLayout.annotations.length; i++) {
        annotations.draw(gd, i);
    }
    return Plotly.Plots.previousPromises(gd);
};

annotations.add = function(gd) {
    var nextAnn = gd._fullLayout.annotations.length;
    Plotly.relayout(gd, 'annotations['+nextAnn+']', 'add');
};

// -----------------------------------------------------
// make or edit an annotation on the graph
// -----------------------------------------------------

// annotations are stored in gd.layout.annotations, an array of objects
// index can point to one item in this array,
//  or non-numeric to simply add a new one
//  or -1 to modify all existing
// opt can be the full options object, or one key (to be set to value)
//  or undefined to simply redraw
// if opt is blank, val can be 'add' or a full options object to add a new
//  annotation at that point in the array, or 'remove' to delete this one
annotations.draw = function(gd, index, opt, value) {
    var layout = gd.layout,
        fullLayout = gd._fullLayout,
        i;

    if(!isNumeric(index) || index===-1) {
        // no index provided - we're operating on ALL annotations
        if(!index && Array.isArray(value)) {
            // a whole annotation array is passed in
            // (as in, redo of delete all)
            layout.annotations = value;
            annotations.supplyLayoutDefaults(layout, fullLayout);
            annotations.drawAll(gd);
            return;
        }
        else if(value==='remove') {
            // delete all
            delete layout.annotations;
            fullLayout.annotations = [];
            annotations.drawAll(gd);
            return;
        }
        else if(opt && value!=='add') {
            // make the same change to all annotations
            for(i = 0; i < fullLayout.annotations.length; i++) {
                annotations.draw(gd, i, opt, value);
            }
            return;
        }
        else {
            // add a new empty annotation
            index = fullLayout.annotations.length;
            fullLayout.annotations.push({});
        }
    }

    if(!opt && value) {
        if(value==='remove') {
            fullLayout._infolayer.selectAll('.annotation[data-index="'+index+'"]')
                .remove();
            fullLayout.annotations.splice(index,1);
            layout.annotations.splice(index,1);
            for(i=index; i<fullLayout.annotations.length; i++) {
                fullLayout._infolayer
                    .selectAll('.annotation[data-index="'+(i+1)+'"]')
                    .attr('data-index',String(i));

                // redraw all annotations past the removed one,
                // so they bind to the right events
                annotations.draw(gd,i);
            }
            return;
        }
        else if(value==='add' || Plotly.Lib.isPlainObject(value)) {
            fullLayout.annotations.splice(index,0,{});

            var rule = Plotly.Lib.isPlainObject(value) ?
                    Plotly.Lib.extendFlat({}, value) :
                    {text: 'New text'};

            if(layout.annotations) {
                layout.annotations.splice(index, 0, rule);
            } else {
                layout.annotations = [rule];
            }

            for(i=fullLayout.annotations.length-1; i>index; i--) {
                fullLayout._infolayer
                    .selectAll('.annotation[data-index="'+(i-1)+'"]')
                    .attr('data-index',String(i));
                annotations.draw(gd,i);
            }
        }
    }

    // remove the existing annotation if there is one
    fullLayout._infolayer.selectAll('.annotation[data-index="'+index+'"]').remove();

    // remember a few things about what was already there,
    var optionsIn = layout.annotations[index],
        oldPrivate = fullLayout.annotations[index];

    // not sure how we're getting here... but C12 is seeing a bug
    // where we fail here when they add/remove annotations
    if(!optionsIn) return;

    var oldRef = {xref: optionsIn.xref, yref: optionsIn.yref};

    // alter the input annotation as requested
    var optionsEdit = {};
    if(typeof opt === 'string' && opt) optionsEdit[opt] = value;
    else if(Plotly.Lib.isPlainObject(opt)) optionsEdit = opt;

    var optionKeys = Object.keys(optionsEdit);
    for(i = 0; i < optionKeys.length; i++) {
        var k = optionKeys[i];
        Plotly.Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]);
    }

    var gs = fullLayout._size;

    var axLetters = ['x', 'y'];
    for(i = 0; i < 2; i++) {
        var axLetter = axLetters[i];
        // if we don't have an explicit position already,
        // don't set one just because we're changing references
        // or axis type.
        // the defaults will be consistent most of the time anyway,
        // except in log/linear changes
        if(optionsEdit[axLetter]!==undefined ||
                optionsIn[axLetter]===undefined) {
            continue;
        }

        var axOld = Plotly.Axes.getFromId(gd,
                Plotly.Axes.coerceRef(oldRef, {}, gd, axLetter)),
            axNew = Plotly.Axes.getFromId(gd,
                Plotly.Axes.coerceRef(optionsIn, {}, gd, axLetter)),
            position = optionsIn[axLetter],
            axTypeOld = oldPrivate['_' + axLetter + 'type'];

        if(optionsEdit[axLetter + 'ref']!==undefined) {
            var autoAnchor = optionsIn[axLetter + 'anchor'] === 'auto',
                plotSize = (axLetter === 'x' ? gs.w : gs.h),
                halfSizeFrac = (oldPrivate['_' + axLetter + 'size'] || 0) /
                    (2 * plotSize);
            if(axOld && axNew) { // data -> different data
                // go to the same fraction of the axis length
                // whether or not these axes share a domain

                // first convert to fraction of the axis
                position = (position - axOld.range[0]) /
                    (axOld.range[1] - axOld.range[0]);

                // then convert to new data coordinates at the same fraction
                position = axNew.range[0] +
                    position * (axNew.range[1] - axNew.range[0]);
            }
            else if(axOld) { // data -> paper
                // first convert to fraction of the axis
                position = (position - axOld.range[0]) /
                    (axOld.range[1] - axOld.range[0]);

                // next scale the axis to the whole plot
                position = axOld.domain[0] +
                    position * (axOld.domain[1] - axOld.domain[0]);

                // finally see if we need to adjust auto alignment
                // because auto always means middle / center alignment for data,
                // but it changes for page alignment based on the closest side
                if(autoAnchor) {
                    var posPlus = position + halfSizeFrac,
                        posMinus = position - halfSizeFrac;
                    if(position + posMinus < 2/3) position = posMinus;
                    else if(position + posPlus > 4/3) position = posPlus;
                }
            }
            else if(axNew) { // paper -> data
                // first see if we need to adjust auto alignment
                if(autoAnchor) {
                    if(position < 1/3) position += halfSizeFrac;
                    else if(position > 2/3) position -= halfSizeFrac;
                }

                // next convert to fraction of the axis
                position = (position - axNew.domain[0]) /
                    (axNew.domain[1] - axNew.domain[0]);

                // finally convert to data coordinates
                position = axNew.range[0] +
                    position * (axNew.range[1] - axNew.range[0]);
            }
        }

        if(axNew && axNew===axOld && axTypeOld) {
            if(axTypeOld==='log' && axNew.type!=='log') {
                position = Math.pow(10,position);
            }
            else if(axTypeOld!=='log' && axNew.type==='log') {
                position = (position>0) ?
                    Math.log(position)/Math.LN10 : undefined;
            }
        }

        optionsIn[axLetter] = position;
    }

    var options = handleAnnotationDefaults(optionsIn, fullLayout);
    fullLayout.annotations[index] = options;

    var xa = Plotly.Axes.getFromId(gd, options.xref),
        ya = Plotly.Axes.getFromId(gd, options.yref),
        annPosPx = {x: 0, y: 0},
        textangle = +options.textangle || 0;

    // create the components
    // made a single group to contain all, so opacity can work right
    // with border/arrow together this could handle a whole bunch of
    // cleanup at this point, but works for now
    var anngroup = fullLayout._infolayer.append('g')
        .classed('annotation', true)
        .attr('data-index', String(index))
        .style('opacity', options.opacity)
        .on('click', function() {
            gd._dragging = false;
            gd.emit('plotly_clickannotation', {
                index: index,
                annotation: optionsIn,
                fullAnnotation: options
            });
        });

    // another group for text+background so that they can rotate together
    var anng = anngroup.append('g')
        .classed('annotation-text-g', true)
        .attr('data-index', String(index));

    var ann = anng.append('g');

    var borderwidth = options.borderwidth,
        borderpad = options.borderpad,
        borderfull = borderwidth + borderpad;

    var annbg = ann.append('rect')
        .attr('class','bg')
{'fraction_non_alphanumeric': 0.1356951359632325, 'fraction_numerical': 0.005821524320183838, 'mean_word_length': 1.8643045195261079, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4721364', 'n_tokens_mistral': 7626, 'n_tokens_neox': 7149, 'n_words': 1873}