wurmlab/sequenceserver

View on GitHub
public/js/circosjs.js

Summary

Maintainability
F
2 wks
Test Coverage
const d3 = require('d3');

// ribbon function from d3 version 3
var π = Math.PI, halfπ = π / 2;
function d3_functor(v) {
    return typeof v === "function" ? v : function() {
        return v;
    };
}

function d3_source(d) {
  return d.source;
}

function d3_target(d) {
  return d.target;
}

function d3_svg_chordRadius(d) {
  return d.radius;
}

function d3_svg_arcStartAngle(d) {
  return d.startAngle;
}

function d3_svg_arcEndAngle(d) {
  return d.endAngle;
}

var ribbonV3 = function() {
    var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
    function chord(d, i) {
        var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
        result = "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.p0) : curve(t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(s.p0)) + "Z";
        return result;
    }
    function subgroup(self, f, d, i) {
        var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ;
        return {
            r: r,
            a0: a0,
            a1: a1,
            p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
            p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
        };
    }
    function equals(a, b) {
        return a.a0 == b.a0 && a.a1 == b.a1;
    }
    function arc(r, p, a) {
        return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
    }
    function curve(p1) {
        return "Q 0,0 " + p1;
    }
    chord.radius = function(v) {
        if (!arguments.length) return radius;
        radius = d3_functor(v);
        return chord;
    };
    chord.source = function(v) {
        if (!arguments.length) return source;
        source = d3_functor(v);
        return chord;
    };
    chord.target = function(v) {
        if (!arguments.length) return target;
        target = d3_functor(v);
        return chord;
    };
    chord.startAngle = function(v) {
        if (!arguments.length) return startAngle;
        startAngle = d3_functor(v);
        return chord;
    };
    chord.endAngle = function(v) {
        if (!arguments.length) return endAngle;
        endAngle = d3_functor(v);
        return chord;
    };
    return chord;
};
// end of ribbon function from d3 version 3


// Bellow code extracted from circosjs https://github.com/nicgirault/circosJS and d3 version upgraded to use version 7.9

var circosJS,
    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

if (typeof Object.assign !== 'function') {
    Object.assign = function(target) {
        'use strict';
        var index, key, source;
        if (target === null) {
            throw new TypeError('Cannot convert undefined or null to object');
        }
        target = Object(target);
        index = 1;
        while (index < arguments.length) {
            source = arguments[index];
            if (source !== null) {
                for (key in source) {
                    if (Object.prototype.hasOwnProperty.call(source, key)) {
                        target[key] = source[key];
                    }
                }
            }
            index++;
        }
        return target;
    };
}

circosJS = function(conf) {
    var instance;
    instance = new circosJS.Core(conf);
    return instance;
};

circosJS.Core = function(conf) {
    this.tracks = {
        heatmaps: {},
        histograms: {},
        chords: {},
        scatters: {},
        lines: {},
        stacks: {},
        highlights: {},
        texts: {}
    };
    this.conf = circosJS.mixConf(conf, this.defaultConf);
    return this;
};

circosJS.Core.prototype.removeTracks = function(trackIds) {
    var id, l, len, ref, store, svg, trackId, type;
    svg = d3.select(this.conf.container);
    ref = this.tracks;
    for (type in ref) {
        store = ref[type];
        if (typeof trackIds === 'object') {
            for (l = 0, len = trackIds.length; l < len; l++) {
                id = trackIds[l];
                if (id in store) {
                    svg.select('.' + id).remove();
                    delete store[id];
                }
            }
        } else if (typeof trackIds === 'string') {
            if (trackIds in store) {
                svg.select('.' + trackIds).remove();
                delete store[trackIds];
            }
        } else if (typeof trackIds === 'undefined') {
            for (trackId in store) {
                svg.select('.' + trackId).remove();
                delete store[trackId];
            }
        }
    }
    return this;
};

circosJS.Core.prototype.layout = function(conf, data) {
    this._layout = new circosJS.Layout(conf, data);
    return this;
};

circosJS.log = function(level, code, message, data) {
    var levels;
    levels = ['Permanent log', 'Error', 'Warning', 'Info'];
    console.log('CircosJS: ', levels[level] + ' [' + code + '] ', message, data);
};

circosJS.mixConf = function(conf, defaultConf) {
    var key, newConf, value;
    newConf = {};
    for (key in defaultConf) {
        value = defaultConf[key];
        if (key in conf) {
            if (Object.prototype.toString.call(value) === '[object Array]') {
                newConf[key] = conf[key];
            } else if (typeof value === 'object' && (value != null)) {
                if ((value != null) && Object.keys(value).length === 0) {
                    newConf[key] = conf[key];
                } else {
                    newConf[key] = circosJS.mixConf(conf[key], value);
                }
            } else {
                newConf[key] = conf[key];
            }
        } else {
            newConf[key] = value;
        }
    }
    return newConf;
};

circosJS.Core.prototype.smartBorders = function() {
    var border, borders, currentBorder, l, layout, len, ref, store, track, trackId, trackType, width;
    width = this.conf.defaultTrackWidth;
    layout = {
        'in': this._layout.conf.innerRadius,
        out: this._layout.conf.outerRadius
    };
    borders = [];
    ref = this.tracks;
    for (trackType in ref) {
        store = ref[trackType];
        for (trackId in store) {
            track = store[trackId];
            if (track.conf.innerRadius) {
                borders.push({
                    'in': track.conf.innerRadius,
                    out: track.conf.outerRadius
                });
            }
        }
    }
    borders = borders.sort(function(a, b) {
        if (a.out > b.out) {
            1;
        }
        if (a.out < b.out) {
            -1;
        }
        return 0;
    });
    currentBorder = layout;
    for (l = 0, len = borders.length; l < len; l++) {
        border = borders[l];
        if (border.out < currentBorder['in'] - width) {
            return {
                'in': currentBorder['in'] - width,
                out: currentBorder['in']
            };
        }
        currentBorder = border;
    }
    if (currentBorder['in'] > width) {
        return {
            'in': currentBorder['in'] - width,
            out: currentBorder['in']
        };
    } else {
        return {
            'in': borders[0].out,
            out: borders[0].out + width
        };
    }
};

if (typeof module !== 'undefined' && module !== null) {
    module.exports = circosJS;
}

circosJS.checkParent = function(key, index, layoutSummary, header) {
    if (!(key in layoutSummary)) {
        circosJS.log(1, 'datum', 'unknown parent id', {
            line: index + 1,
            value: key,
            header: header,
            layoutSummary: layoutSummary
        });
        return false;
    }
    return true;
};

circosJS.checkNumber = function(keys, index) {
    var header, value;
    for (header in keys) {
        value = keys[header];
        if (isNaN(value)) {
            circosJS.log(1, 'datum', 'not a number', {
                line: index + 1,
                value: value,
                header: header
            });
            return false;
        }
    }
    return true;
};

circosJS.parseChordData = function(data, layoutSummary) {
    var sample;
    sample = data[0];
    if ('source_id' in sample && 'source_start' in sample && 'source_end' && 'target_id' in sample && 'target_start' in sample && 'target_end' in sample) {
        data = data.map(function(datum) {
            var elts;
            elts = [datum.source_id, datum.source_start, datum.source_end, datum.target_id, datum.target_start, datum.target_end];
            if (datum.value != null) {
                elts.push(datum.value);
            }
            return elts;
        });
    }
    data = data.filter(function(datum, index) {
        return circosJS.checkParent(datum[0], index, layoutSummary, 'source_id');
    }).filter(function(datum, index) {
        return circosJS.checkParent(datum[3], index, layoutSummary, 'target_id');
    }).filter(function(datum, index) {
        return circosJS.checkNumber({
            source_start: datum[1],
            source_end: datum[2],
            target_start: datum[4],
            target_end: datum[5],
            value: datum[6] || 1
        }, index);
    }).map(function(datum) {
        return {
            source: {
                id: datum[0],
                start: Math.max(0, parseFloat(datum[1])),
                end: Math.min(layoutSummary[datum[0]], parseFloat(datum[2]))
            },
            target: {
                id: datum[3],
                start: Math.max(0, parseFloat(datum[4])),
                end: Math.min(layoutSummary[datum[3]], parseFloat(datum[5]))
            },
            value: parseFloat(datum[6]),
            hsp: datum[7]
        };
    });
    return {
        data: data,
        meta: {
            min: d3.min(data, function(d) {
                return d.value;
            }),
            max: d3.max(data, function(d) {
                return d.value;
            })
        }
    };
};

circosJS.Layout = function(conf, data) {
    var block_nb, gap, k, offset, ref, ref1, size, v;
    if (data == null) {
        circosJS.log(2, 'no layout data', '');
    }
    this.conf = circosJS.mixConf(conf, JSON.parse(JSON.stringify(this.defaultConf)));
    this.data = data;
    this.blocks = {};
    this.size = 0;
    offset = 0;
    ref = this.data;
    for (k in ref) {
        v = ref[k];
        this.blocks[v.id] = {
            label: v.label,
            len: v.len,
            color: v.color,
            offset: offset
        };
        v.offset = offset;
        offset += v.len;
    }
    this.size = offset;
    gap = this.conf.gap;
    size = this.size;
    block_nb = this.data.length;
    ref1 = this.data;
    for (k in ref1) {
        v = ref1[k];
        this.blocks[v.id].start = v.offset / size * (2 * Math.PI - block_nb * gap) + k * gap;
        this.blocks[v.id].end = (v.offset + v.len) / size * (2 * Math.PI - block_nb * gap) + k * gap;
        v.start = v.offset / size * (2 * Math.PI - block_nb * gap) + k * gap;
        v.end = (v.offset + v.len) / size * (2 * Math.PI - block_nb * gap) + k * gap;
    }
    this.getAngle = function(blockId, unit) {
        var block;
        block = this.blocks[blockId].start / this._size;
        if (unit === 'deg') {
            return block * 360;
        } else if (unit === 'rad') {
            return block * 2 * Math.PI;
        } else {
            return null;
        }
    };
    this.summary = function() {
        var d, l, layoutSummary, len, ref2;
        layoutSummary = {};
        ref2 = this._data;
        for (l = 0, len = ref2.length; l < len; l++) {
            d = ref2[l];
            layoutSummary[d.id] = d.len;
        }
        return layoutSummary;
    };
    return this;
};

circosJS.Core.prototype.chord = function(id, conf, data) {
    var track;
    track = new circosJS.Chord();
    track.build(this, conf, data);
    this.tracks.chords[id] = track;
    return this;
};

circosJS.Chord = function() {
    circosJS.Track.call(this);
    this.parseData = circosJS.parseChordData;
    this.applyRules = function(rules, data) {
        var datum, l, len, results, rule;
        rules = rules || [];
        results = [];
        for (l = 0, len = data.length; l < len; l++) {
            datum = data[l];
            results.push((function() {
                var len1, m, results1;
                results1 = [];
                for (m = 0, len1 = rules.length; m < len1; m++) {
                    rule = rules[m];
                    if (rule.condition(datum)) {
                        results1.push(datum[rule.parameter] = rule.value);
                    } else {
                        results1.push(void 0);
                    }
                }
                return results1;
            })());
        }
        return results;
    };
    this.getSource = (function(_this) {
        return function(d, layout) {
            var block, endAngle, result, startAngle;
            d = d.source;
            block = layout.blocks[d.id];
            startAngle = block.start + d.start / block.len * (block.end - block.start);
            endAngle = block.start + d.end / block.len * (block.end - block.start);
            result = {
                radius: layout.conf.innerRadius,
                startAngle: startAngle,
                endAngle: endAngle
            };
            return result;
        };
    })(this);
    this.getTarget = (function(_this) {
        return function(d, layout) {
            var block, endAngle, result, startAngle;
            d = d.target;
            block = layout.blocks[d.id];
            startAngle = block.start + d.start / block.len * (block.end - block.start);
            endAngle = block.start + d.end / block.len * (block.end - block.start);
            result = {
                radius: layout.conf.innerRadius,
                startAngle: startAngle,
                endAngle: endAngle
            };
            return result;
        };
    })(this);
    this.dimChords = function (parentElement, d, conf, index) {
        parentElement.selectAll('path').style('opacity', function (p, i) {
            if (d === p) {
                return 1;
            }
            return 0;
        });
    };
    this.resetChords = function (parentElement,conf) {
        parentElement.selectAll('path').style('opacity',conf.opacity);
    };
    this.renderChords = function(parentElement, name, conf, data, layout, ratio, getSource, getTarget) {
        var link, track;
        track = parentElement.append('g').attr('class', conf.colorPalette);
        link = track
            .selectAll('.chord')
            .data(data)
            .enter()
            .append('path')
            .attr('class', 'chord')
            .attr('d', ribbonV3()
                .source(function(d) {
                    return getSource(d, layout);
                })
                .target(function(d) {
                    return getTarget(d, layout);
                })
            )
            .attr('opacity', function(_d) {
                return conf.opacity;
            })
            .attr('id', function(d) {
                return d.source.id + '_' + d.target.id;
            })
            .on('mouseover', (function(_this) {
                return function(event, d) {
                    _this.dimChords(track, d, conf);
                    return _this.dispatch.call('mouseover', event, d);
                };
            })(this))
            .on('mouseout', (function(_this) {
                return function(event, d) {
                    _this.resetChords(track, conf);
                    return _this.dispatch.call('mouseout', event, d);
                };
            })(this));

        if (conf.usePalette) {
            link.attr('class', function(d) {
                return 'q' + ratio(d.value, conf.cmin, conf.cmax, conf.colorPaletteSize, conf.colorPaletteReverse, conf.logScale) + '-' + conf.colorPaletteSize;
            });
        } else {
            link.attr('fill', function(d) {
                return d.color || conf.color;
            });
        }
        return link;
    };
    this.render = (function(_this) {
        return function(instance, parentElement, name) {
            var selection, track;
            parentElement.select('.' + name).remove();
            track = parentElement.append('g').attr('class', name).attr('z-index', _this.conf.zIndex);
            selection = _this.renderChords(track, name, _this.conf, _this.data, instance._layout, _this.ratio, _this.getSource, _this.getTarget);
            if (_this.conf.tooltipContent != null) {
                return circosJS.registerTooltip(instance, _this, selection, _this.conf);
            }
        };
    })(this);
    return this;
};

circosJS.Track = function() {
    this.build = function(instance, conf, data) {
        this.dispatch = d3.dispatch('mouseover', 'mouseout');
        this.loadData(data, instance);
        this.conf = this.processConf(conf, this.defaultConf, this.meta, instance, this);
        return this.applyRules(conf.rules, this.data);
    };
    this.loadData = function(data, instance) {
        var d, l, layoutSummary, len, ref, result;
        layoutSummary = {};
        ref = instance._layout.data;
        for (l = 0, len = ref.length; l < len; l++) {
            d = ref[l];
            layoutSummary[d.id] = d.len;
        }
        result = this.parseData(data, layoutSummary);
        this.data = result.data;
        return this.meta = result.meta;
    };
    this.processConf = function(conf, defaultConf, meta, instance, utils) {
        var smartBorders;
        conf = circosJS.mixConf(conf, Object.assign({}, defaultConf));
        conf = utils.computeMinMax(conf, meta);
        if (conf.innerRadius === 0 && conf.outerRadius === 0) {
            smartBorders = instance.smartBorders();
            conf.innerRadius = smartBorders['in'];
            conf.outerRadius = smartBorders.out;
        }
        return conf;
    };
    this.applyRules = function(rules, data) {
        var datum, i, k, results, rule, v;
        rules = rules || [];
        results = [];
        for (k in data) {
            v = data[k];
            results.push((function() {
                var ref, results1;
                ref = v.values;
                results1 = [];
                for (i in ref) {
                    datum = ref[i];
                    results1.push((function() {
                        var l, len, results2;
                        results2 = [];
                        for (l = 0, len = rules.length; l < len; l++) {
                            rule = rules[l];
                            if (rule.condition(v.key, datum, i)) {
                                results2.push(datum[rule.parameter] = rule.value);
                            } else {
                                results2.push(void 0);
                            }
                        }
                        return results2;
                    })());
                }
                return results1;
            })());
        }
        return results;
    };
    this.computeMinMax = function(conf, meta) {
        conf.cmin = conf.min === 'smart' ? meta.min : conf.min;
        conf.cmax = conf.max === 'smart' ? meta.max : conf.max;
        return conf;
    };
    this.ratio = function(value, min, max, scope, reverse, logScale) {
        var fraction, scaleLogBase, x;
        scaleLogBase = logScale ? 2.3 : 1;
        if (min === max || (value === min && !reverse) || (value === max && reverse)) {
            return 0;
        }
        if (value === max || (value === min && reverse)) {
            return scope - 1;
        }
        fraction = (value - min) / (max - min);
        x = Math.exp(1 / scaleLogBase * Math.log(fraction));
        if (reverse) {
            x = 1 - x;
        }
        return Math.floor(scope * x);
    };
    this.render = (function(_this) {
        return function(instance, parentElement, name) {
            var datumContainer, ref, selection, track;
            parentElement.select('.' + name).remove();
            track = parentElement.append('g').attr('class', name).attr('z-index', _this.conf.zIndex);
            datumContainer = _this.renderDatumContainer(instance, track, name, _this.data, _this.conf);
            if ((ref = _this.conf.axes) != null ? ref.display : void 0) {
                _this.renderAxes(datumContainer, _this.conf, instance._layout, _this.data);
            }
            selection = _this.renderDatum(datumContainer, _this.conf, instance._layout, _this);
            if (_this.conf.tooltipContent != null) {
                circosJS.registerTooltip(instance, _this, selection, _this.conf);
            }
            selection.on('mouseover', function(d, i, j) {
                return _this.dispatch.mouseover(d, i, j);
            });
            return selection.on('mouseout', function(d, i, j) {
                return _this.dispatch.mouseout(d, i, j);
            });
        };
    })(this);
    this.renderBlock = function(parentElement, data, layout, conf) {
        var block, scope;
        scope = conf.outerRadius - conf.innerRadius;
        block = parentElement.selectAll('.block').data(data).enter().append('g').attr('class', 'block').attr('transform', function(d) {
            return 'rotate(' + layout.blocks[d.key].start * 360 / (2 * Math.PI) + ')';
        });
        if (conf.backgrounds) {
            block.selectAll('.background').data(conf.backgrounds).enter().append('path').attr('class', 'background').attr('fill', function(background) {
                return background.color;
            }).attr('opacity', function(background) {
                return background.opacity || 1;
            }).attr('d', d3.arc().innerRadius(function(background) {
                if (conf.direction === 'in') {
                    return conf.outerRadius - scope * background.start;
                } else {
                    return conf.innerRadius + scope * background.start;
                }
            }).outerRadius(function(background) {
                if (conf.direction === 'in') {
                    return conf.outerRadius - scope * background.end;
                } else {
                    return conf.innerRadius + scope * background.end;
                }
            }).startAngle(function(d, i, j) {
                return 0;
            }).endAngle(function(d, i, j) {
                return layout.blocks[data[j].key].end - layout.blocks[data[j].key].start;
            }));
        }
        return block;
    };
    this.renderAxes = function(parentElement, conf, layout, data) {
        var axes, axis, x;
        if (conf.axes.minor.spacingType === 'pixel') {
            axes = (function() {
                var l, ref, ref1, ref2, results;
                results = [];
                for (x = l = ref = conf.innerRadius, ref1 = conf.outerRadius, ref2 = conf.axes.minor.spacing; ref2 > 0 ? l <= ref1 : l >= ref1; x = l += ref2) {
                    results.push(x);
                }
                return results;
            })();
        }
        axis = d3.arc().innerRadius(function(d) {
            return d;
        }).outerRadius(function(d) {
            return d;
        }).startAngle(0).endAngle(function(d, i, j) {
            var block;
            block = layout.blocks[data[j].key];
            return block.end - block.start;
        });
        console.log('axis', axis);
        return parentElement.selectAll('.axis').data(axes).enter().append('path').attr('opacity', conf.opacity).attr('class', 'axis').attr('d', axis).attr('stroke-width', function(d, i) {
            if (i % conf.axes.major.spacing === 0) {
                return conf.axes.major.thickness;
            } else {
                return conf.axes.minor.thickness;
            }
        }).attr('stroke', function(d, i) {
            if (i % conf.axes.major.spacing === 0) {
                return conf.axes.major.color;
            } else {
                return conf.axes.minor.color;
            }
        });
    };
    this.theta = function(position, block) {
        return position / block.len * (block.end - block.start);
    };
    this.x = (function(_this) {
        return function(d, layout, conf) {
            var angle, height, r;
            height = _this.ratio(d.value, conf.cmin, conf.cmax, conf.outerRadius - conf.innerRadius, false, conf.logscale);
            if (conf.direction === 'in') {
                r = conf.outerRadius - height;
            } else {
                r = conf.innerRadius + height;
            }
            angle = _this.theta(d.position, layout.blocks[d.block_id]) - Math.PI / 2;
            return r * Math.cos(angle);
        };
    })(this);
    this.y = (function(_this) {
        return function(d, layout, conf) {
            var angle, height, r;
            height = _this.ratio(d.value, conf.cmin, conf.cmax, conf.outerRadius - conf.innerRadius, false, conf.logscale);
            if (conf.direction === 'in') {
                r = conf.outerRadius - height;
            } else {
                r = conf.innerRadius + height;
            }
            angle = _this.theta(d.position, layout.blocks[d.block_id]) - Math.PI / 2;
            return r * Math.sin(angle);
        };
    })(this);
    this.ratio = function(value, min, max, scope, reverse, logScale) {
        var fraction, scaleLogBase, x;
        scaleLogBase = logScale ? 2.3 : 1;
        if (min === max || (value === min && !reverse) || (value === max && reverse)) {
            return 0;
        }
        if (value === max || (value === min && reverse)) {
            return scope - 1;
        }
        fraction = (value - min) / (max - min);
        x = Math.exp(1 / scaleLogBase * Math.log(fraction));
        if (reverse) {
            x = 1 - x;
        }
        return Math.floor(scope * x);
    };
    return this;
};

circosJS.renderLayout = function(d3, parentElement, instance) {
    var block, conf, entry, layout;
    conf = instance._layout.conf;
    parentElement.select('.cs-layout').remove();
    layout = parentElement.append('g').attr('class', 'cs-layout').attr('z-index', conf.zIndex).on('click', conf.onClick);
    block = layout.selectAll('g').data(instance._layout.data).enter().append('g').attr('class', function(d) {
        return d.id;
    }).attr('opacity', conf.opacity);
    entry = d3.arc().innerRadius(conf.innerRadius).outerRadius(conf.outerRadius).cornerRadius(conf.cornerRadius).startAngle(function(d) {
        return d.start;
    }).endAngle(function(d) {
        return d.end;
    });
    block.append('path').attr('d', entry).attr('fill', function(d) {
        return d.color;
    }).attr('id', function(d) {
        return d.id;
    });
    if (conf.labels.display) {
        circosJS.renderLayoutLabels(conf, d3, block);
    }
    if (conf.ticks.display) {
        return circosJS.renderLayoutTicks(conf, layout, d3, instance);
    }
};

circosJS.renderLayoutLabels = function(conf, d3, block) {
    var label, labelArc, r;
    r = conf.innerRadius + conf.labels.radialOffset;
    labelArc = d3.arc().innerRadius(r).outerRadius(r).startAngle(function(d, i) {
        return d.start;
    }).endAngle(function(d, i) {
        return d.end;
    });
    block.append('path').attr('fill', 'none').attr('stroke', 'none').attr('d', labelArc).attr('id', function(d) {
        return 'arc-label' + d.id;
    });
    label = block.append('text').attr('font-size', conf.labels.size).attr('text-anchor', 'middle');
    return label.append('textPath').attr('startOffset', '25%').attr('xlink:href', function(d) {
        return '#arc-label' + d.id;
    }).style('fill', conf.labels.color).text(function(d) {
        var arc_len = d.end - d.start;
        return d.label.slice(0, Math.floor(21 * arc_len)) + '..';
    });
};

circosJS.renderLayoutTicks = function(conf, layout, d3, instance) {
    var blockTicks, displayLabel, ticks;
    blockTicks = function(d) {
        var k,space;
        k = (d.end - d.start) / d.len;
        if ((d.end - d.start) > 1.57) {
            space = 8;
        } else if ((d.end - d.start) > 0.785) {
            space = 4;
        } else if ((d.end - d.start) > 0.3925) {
            space = 2;
        } else if ((d.end - d.start) > 0) {
            space = 0;
        }
        var arr = [];
        var item = {angle: 0 * k + d.start, label: 0};
        arr.push(item);
        var temp_scale = d3.scaleLinear()
            .domain([1, d.len])
            .range([d.start, d.end]);
        var len = temp_scale.ticks(space).length;
        temp_scale.ticks(space).map(function (v,i) {
            var init = v * k + d.start;
            var final = d.len * k +d.start;
            if ((final - init) > 0.5) {
            }
            var item = {
                angle: v * k + d.start,
                label: v / conf.ticks.labelDenominator + conf.ticks.labelSuffix
            };
            arr.push(item);
        });
        arr.splice(len, 1);
        var item = {
            angle: (d.len * k + d.start) - 0.006,
            label: d.len / conf.ticks.labelDenominator + conf.ticks.labelSuffix
        };
        arr.push(item);
        return arr;
    };
    displayLabel = function(v, i) {
        if (conf.ticks.labels === false) {
            return null;
        } else if (conf.ticks.labelDisplay0 === false && i === 0) {
            return null;
        } else if (i % conf.ticks.labelSpacing) {
            return null;
        } else if (i === 0) {
            return v / conf.ticks.labelDenominator; // added support for 0 without suffix.
        }
        else {
            return v / conf.ticks.labelDenominator + conf.ticks.labelSuffix;
        }
    };
    ticks = layout.append('g').selectAll('g').data(instance._layout.data).enter().append('g').selectAll('g').data(blockTicks).enter().append('g').attr('transform', function(d) {
        return 'rotate(' + (d.angle * 180 / Math.PI - 90) + ')' + 'translate(' + conf.outerRadius + ',0)';
    });
    ticks.append('line').attr('x1', 0).attr('y1', 1).attr('x2', function(d, i) {
        return conf.ticks.size.major;
    }).attr('y2', 1).style('stroke', conf.ticks.color);
    return ticks.append('text').attr('x', 8).attr('dy', '.35em').attr('transform', function(d) {
        if (d.angle > Math.PI) {
            return 'rotate(180)translate(-16)';
        } else {
            return null;
        }
    }).attr('text-anchor', function(d) {
        if (d.angle > Math.PI) {
            return 'end';
        } else {
            return null;
        }
    }).attr('font-size', conf.ticks.labelSize).style('fill', conf.ticks.labelColor).text(function(d) {
        return d.label;
    });
};

circosJS.Core.prototype.render = function(ids, removeTracks) {
    var name, ref, ref1, renderAll, svg, track, trackStore, trackType, tracks, translated;
    if (typeof ids === 'undefined') {
        renderAll = true;
        ids = [];
    }
    if (removeTracks) {
        ref = this.tracks;
        for (trackType in ref) {
            trackStore = ref[trackType];
            for (name in trackStore) {
                track = trackStore[name];
                svg.select('.' + name).remove();
            }
        }
    }
    svg = d3.select(this.conf.container);
    translated = svg.select('.all');
    if (translated.empty()) {
        translated = svg.append('svg')
              .attr('width', this.conf.width)
              .attr('height', this.conf.height)

        translated = translated.append('g').attr('class', 'all').attr('transform', 'translate(' + parseInt(this.conf.width / 2) + ',' + parseInt(this.conf.height / 2) + ')');
    }
    ref1 = this.tracks;
    for (trackType in ref1) {
        trackStore = ref1[trackType];
        for (name in trackStore) {
            track = trackStore[name];
            if (renderAll || indexOf.call(ids, name) >= 0) {
                track.render(this, translated, name);
            }
        }
    }
    if (renderAll || indexOf.call(ids, 'layout') >= 0) {
        circosJS.renderLayout(d3, translated, this);
    }

    // tracks = svg.selectAll('.all > g').nodes();
    // svg.selectAll('.all > g').remove();

    // tracks.sort(function(a, b) {
    //     if (parseInt(a.getAttribute('z-index')) < parseInt(b.getAttribute('z-index'))) {
    //         return -1;
    //     } else if (parseInt(a.getAttribute('z-index')) > parseInt(b.getAttribute('z-index'))) {
    //         return 1;
    //     } else {
    //         return 0;
    //     }
    // });

    // svg.select('.all').selectAll('g').data(tracks).enter().append(function(d) {
    //     return d;
    // });
    // return this;
};

circosJS.Core.prototype.defaultConf = {
    width: 700,
    height: 700,
    container: 'circos',
    defaultTrackWidth: 10
};

circosJS.Layout.prototype.defaultConf = {
    innerRadius: 250,
    outerRadius: 300,
    cornerRadius: 5,
    gap: 0.04,
    opacity: 1,
    labels: {
        position: 'center',
        display: true,
        size: 14,
        color: '#000',
        radialOffset: 20
    },
    ticks: {
        display: true,
        color: 'grey',
        spacing: 10000000,
        labels: true,
        labelSpacing: 10,
        labelSuffix: 'Mb',
        labelDenominator: 1000000,
        labelDisplay0: true,
        labelSize: 10,
        labelColor: '#000',
        labelFont: 'default',
        majorSpacing: 5,
        size: {
            minor: 2,
            major: 5
        }
    },
    onClick: null,
    onMouseOver: null,
    zIndex: 100
};

circosJS.axes = {
    display: false,
    minor: {
        spacing: 5,
        spacingType: 'pixel',
        color: '#d3d3d3',
        thickness: 2
    },
    major: {
        spacing: 5,
        color: '#000000',
        thickness: 2
    }
};

circosJS.Chord.prototype.defaultConf = {
    colorPaletteSize: 9,
    colorPalette: 'PuBuGn',
    usePalette: true,
    colorPaletteReverse: false,
    color: '#fd6a62',
    opacity: 0.7,
    min: 'smart',
    max: 'smart',
    logScale: false,
    rules: [],
    backgrounds: [],
    zIndex: 1,
    tooltipContent: null
};

circosJS.registerTooltip = function(instance, track, element, trackParams) {
    track.tip = d3.tip().direction('s').offset([20, 0]).html(trackParams.tooltipContent);
    element.call(track.tip);
    track.dispatch.on('mouseover', function(d, i, j) {
        return track.tip.attr('class', 'd3-tip appear').show(d);
    });
    return track.dispatch.on('mouseout', function(d, i, j) {
        track.tip.attr('class', 'd3-tip').show(d);
        return track.tip.hide();
    });
};