/*
Copyright 2012-2020 VeloViewer. All Rights Reserved
###V143###
*/
var embed = false;
var curLat = 0;
var curLng = 0;
var c = [];
var filterActiveColumns = [];
var brushes = [];
var rowCount = 20;
var offset = 0;
var orderCol;
var orderDir = 'desc';
var a = [];
var errorDisplayed = false;
var ajaxBatchSize = 10;
var curConfig = 0;
var gettingPos = false;
var selectedConfId = 0;
var theadChanginged = true;
var segArr;
var lastFocus;
var liveData, liveDataObj;
var origConfig;
var editingCol;
var filtersFinishedTimer;

Array.prototype.getUnique = function() {
    var u = {},
        a = [];
    for (var i = 0, l = this.length; i < l; ++i) {
        if (u.hasOwnProperty(this[i])) {
            continue;
        }
        a.push(this[i]);
        u[this[i]] = 1;
    }
    return a;
}

var hueScale = d3.scale.linear()
    .domain([1, 100])
    .range([100, 0]);

var opacityScale = d3.scale.linear()
    .domain([1, 50])
    .range([0.1, 1]);

function writeURL() {
    cacheConfig();
    if (typeof(chartTypeVal) !== 'undefined' && window.history.replaceState) {
        var newUrl = document.location.pathname + '?o=' + orderCol.id + ':' + (orderDir == 'asc' ? '0' : '1');

        if (filterActiveColumns.length > 0) {
            newUrl += '&f=' + filterActiveColumns.map(function(d) {
                switch (d.type) {
                    case 'list':
                        if (typeof(d.filterSelectedItems) === 'undefined' || d.filterSelectedItems.join() == d.keys.join()) {
                            return d.id + ':All'
                        }
                        return d.id + ':' + d.filterSelectedItems.join('|');
                        break;
                    case 'num':
                    case 'date':
                    case 'lrgPace':
                    case 'smlPace':
                    case 'geo':
                        return d.id + ':' + (d.filterMin == d.min ? '-1000000' : d.filterMin) + '|' + (d.filterMax == d.max ? '1000000' : d.filterMax);
                        break;
                    case 'text':
                        return d.id + ':' + encodeURIComponent(d.filterVal);
                        break;
                    default:
                        return ''
                }
            }).filter(function(d) {
                return d != ''
            }).join(',');
        }

        if (typeof(chartYCols) === 'undefined') {
            newUrl += '&c=' + chartTypeVal + ',' + chartDefX + ',' + chartDefY + ',' + chartDefZ + ',' + chartDefC;
        } else {
            newUrl += '&c=' + chartTypeVal + ',' + chartXCol.id + ',' + (chartYCols[0].y == null ? -1 : chartYCols[0].y.id) + ',' + (chartYCols[0].z == null ? -1 : chartYCols[0].z.id) + ',' + (chartYCols[0].c == null ? -1 : chartYCols[0].c.id);
        }

        window.history.replaceState("", "", newUrl);
        d3.selectAll('.a2a_kit a').attr('href', function(d) {
            return d3.select(this).attr('href') == null ? null : d3.select(this).attr('href').replace(/linkurl=[^&]*/, 'linkurl=' + encodeURIComponent(window.document.location.href))
        });
    }
}

function clearTextSelection() {
    if (window.getSelection) {
        if (window.getSelection().empty) {
            window.getSelection().empty();
        } else if (window.getSelection().removeAllRanges) {
            window.getSelection().removeAllRanges();
        }
    } else if (document.selection) {
        document.selection.empty();
    }
}

function initConfig() {
    d3.select('#configSelect')
        .selectAll('option')
        .remove();

    d3.select('#configSelect')
        .selectAll('option')
        .data(c)
        .enter()
        .append('option')
        .text(function(d) {
            return d.name
        });

    d3.select('#configSelect')
        .on('change', null);

    document.getElementById('configSelect').selectedIndex = selectedConfId;

    d3.select('#configSelect')
        .on('change', function(d, i) {
            //curConfig = parseInt(d3.select(this.selectedOptions)[0][0][0].__data__.id);
            curConfig = parseInt(d3.select(document.getElementById('configSelect').options[document.getElementById('configSelect').selectedIndex]).data()[0].id);
            origConfig = curConfig;
            setConfig();
            setFilterActive();
            setMainControls();
            setOrder(a, true);
            dataLoaded();
            updateActiveFilters();
            updateAvailableFilters();
        });
}

function initChartOptions() {
    var chartTypes = [{
        "id": 0,
        "desc": 'Scatter'
    }, {
        "id": 1,
        "desc": 'Column'
    }, {
        "id": 2,
        "desc": 'Line'
    }];
    d3.select('#chartType')
        .on('change', function(d, i) {
            chartTypeVal = d3.select(document.getElementById('chartType').options[document.getElementById('chartType').selectedIndex]).data()[0].id;
            var th = d3.select('#chart svg').style('height');
            d3.select('#chart svg').remove();
            d3.select('#chart').append('svg').style({
                'width': '100%',
                'height': th
            });
            drawChart();
        })
        .selectAll('option')
        .data(chartTypes)
        .enter()
        .append('option')
        .attr('selected', function(d) {
            return (d.id == chartTypeVal ? 'selected' : null)
        })
        .text(function(d) {
            return d.desc
        });

    d3.select('#chartXAxis')
        .on('change', function(d, i) {
            chartXCol = d3.select(document.getElementById('chartXAxis').options[document.getElementById('chartXAxis').selectedIndex]).data()[0];
            drawChart();
        })
        .selectAll('option')
        .data(columns.filter(function(d) {
            return d.type == 'list' || d.type == 'num' || d.type == 'date' || d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'geo'
        }).sort(function(a, b) {
            return (a.desc < b.desc ? -1 : 1)
        }))
        .enter()
        .append('option')
        .attr('selected', function(d) {
            return (d.id == chartDefX ? 'selected' : null)
        })
        .text(function(d) {
            return d.desc
        });

    d3.select('#chartYAxis')
        .on('change', function(d, i) {
            chartYCols[0].y = d3.select(document.getElementById('chartYAxis').options[document.getElementById('chartYAxis').selectedIndex]).data()[0];
            drawChart();
        })
        .selectAll('option')
        .data(columns.filter(function(d) {
            return d.type == 'num' || d.type == 'date' || d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'geo'
        }).sort(function(a, b) {
            return (a.desc < b.desc ? -1 : 1)
        }))
        .enter()
        .append('option')
        .attr('selected', function(d) {
            return (d.id == chartDefY ? 'selected' : null)
        })
        .text(function(d) {
            return d.desc
        });

    d3.select('#chartZAxis')
        .on('change', function(d, i) {
            if (document.getElementById('chartZAxis').selectedIndex == 0) {
                chartYCols[0].z = null;
            } else {
                chartYCols[0].z = d3.select(document.getElementById('chartZAxis').options[document.getElementById('chartZAxis').selectedIndex]).data()[0];
            }
            drawChart();
        })
        .selectAll('option')
        .data([{
            "desc": 'None',
            "id": -1
        }].concat(columns.filter(function(d) {
            return d.type == 'num' || d.type == 'date' || d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'geo'
        }).sort(function(a, b) {
            return (a.desc < b.desc ? -1 : 1)
        })))
        .enter()
        .append('option')
        .attr('selected', function(d) {
            return (d.id == chartDefZ ? 'selected' : null)
        })
        .text(function(d) {
            return d.desc
        });

    d3.select('#chartCAxis')
        .on('change', function(d, i) {
            if (document.getElementById('chartCAxis').selectedIndex == 0) {
                chartYCols[0].c = null;
            } else {
                chartYCols[0].c = d3.select(document.getElementById('chartCAxis').options[document.getElementById('chartCAxis').selectedIndex]).data()[0];
            }
            drawChart();
        })
        .selectAll('option')
        .data([{
            "desc": 'None',
            "id": -1
        }].concat(columns.filter(function(d) {
            return d.type == 'list' || d.type == 'num' || d.type == 'date' || d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'geo'
        }).sort(function(a, b) {
            return (a.desc < b.desc ? -1 : 1)
        })))
        .enter()
        .append('option')
        .attr('selected', function(d) {
            return (d.id == chartDefC ? 'selected' : null)
        })
        .text(function(d) {
            return d.desc
        });
}

function setConfig() {
    initOther();
    theadChanginged = true;
    selectedConfId = jsonIndexOf(c, 'id', curConfig.toString());
    if (selectedConfId == -1) {
        selectedConfId = 0;
    }
    var conf = c[selectedConfId];
    conf.colOrder = conf.colOrder.replace(/,$/i, "");

    d3.select('#saveConfigModalBtn')
        .attr('disabled', (conf.temp ? null : 'disabled'));

    if (typeof(confCookie) !== 'undefined' && typeof(conf.temp) !== 'undefined' && !conf.temp) {
        setCookie(confCookie, conf.id, 365);
    }
    var co = conf.colOrder.split(',');
    var cdo = conf.dataOrder.split(':');
    var cd = (typeof(conf.colDisp) !== 'undefined' ? conf.colDisp.split(',') : []);
    var cf = (conf.filters != null && conf.filters != '' ? conf.filters.replace(/,([0-9]+:)/g, '#####$1').split('#####') : []);

    orderCol = columns[jsonIndexOf(columns, 'id', parseInt(cdo[0]))];
    orderDir = (parseInt(cdo[1]) == 0 ? 'asc' : 'desc');

    columns.filter(function(d) {
        return typeof(d.id) !== 'undefined'
    }).forEach(function(d, i) {
        d.displayOrder = co.indexOf(d.id.toString());
        d.respDisp = (typeof(cd[d.displayOrder]) !== 'undefined' ? cd[d.displayOrder] : 0);
        d.filterActive = 0;

        cf.forEach(function(dd, ii) {
            var cfi = dd.split(':');
            if (d.id == cfi[0]) {
                d.filterActive = 1;

                initFilter(d);

                switch (d.type) {
                    case 'list':
                        if (cfi[1] != 'All') {
                            d.filterSelectedItems = cfi[1].split('|');
                        } else {
                            delete d.filterSelectedItems;
                        }
                        break;
                    case 'text':
                        d.filterVal = cfi[1];
                        break;
                    default:
                        var mm = cfi[1].split('|');
                        if (cfi[1] == '-1000000|1000000') {
                            d.filterMin = d.min;
                            d.filterMax = d.max;
                        } else {
                            d.filterMin = Math.max(parseFloat(mm[0]), d.min);
                            d.filterMax = (mm[1] == 1000000 ? d.max : Math.min(parseFloat(mm[1]), d.max));
                        }

                        break;
                }
            }
        });
    });

    setConfigOptions();
}

function cacheConfig() {
    if (origConfig == null) {
        origConfig = curConfig;
    }
    var ci = jsonIndexOf(c, 'id', origConfig.toString());
    d3.select('#saveConfigModalBtn')
        .attr('disabled', null);

    var tIndex = jsonIndexOf(c, 'temp', true);

    var l_conf = {
        "colOrder": columns.filter(function(d) {
                return d.displayOrder > -1
            })
            .sort(function(a, b) {
                return a.displayOrder - b.displayOrder
            })
            .map(function(d) {
                return d.id
            }).join(','),
        "colDisp": columns.filter(function(d) {
                return d.displayOrder > -1
            })
            .sort(function(a, b) {
                return a.displayOrder - b.displayOrder
            })
            .map(function(d) {
                return d.respDisp
            }).join(','),
        "dataOrder": orderCol.id + ':' + (orderDir == 'asc' ? '0' : '1'),
        "filters": columns.filter(function(d) {
                return d.filterActive
            })
            .map(function(d) {
                switch (d.type) {
                    case 'list':
                        return d.id + ':' + (typeof(d.filterSelectedItems) === 'undefined' || d.filterSelectedItems.length == d.keys.length ? 'All' : d.filterSelectedItems.join('|'));
                        break;
                    default:
                        return d.id + ':' + (d.filterMin == d.min ? '-1000000' : d.filterMin) + '|' + (d.filterMax == d.max ? '1000000' : d.filterMax);
                        break;
                }
            }).join(','),
        "name": 'Custom', //'Not saved!',
        "id": tIndex == -1 ? '1000000' : c[tIndex].id,
        "editable": true,
        "temp": true
    };

    if (ci == -1 ||
        c[ci].colOrder != l_conf.colOrder ||
        //c[ci].colDisp != l_conf.colDisp ||
        c[ci].dataOrder != l_conf.dataOrder ||
        c[ci].filters != l_conf.filters) {
        if (tIndex == -1) {
            tIndex = c.length;
        }
        c[tIndex] = l_conf;
        selectedConfId = tIndex;
    }

    initConfig();
}

function setConfigSaveOptions(obj) {
    if (obj.checked) {
        d3.selectAll('.configOptions [disabled=disabled]')
            .attr('disabled', null);

        switch (d3.select(obj).attr("data-item")) {
            case '0':
                d3.selectAll('#configOverwrite, #deleteConfigBtn')
                    .attr('disabled', 'disabled');
                d3.select('#saveConfigBtn').text('Create new config');
                break;
            default:
                d3.select('#configCreate')
                    .attr('disabled', 'disabled');
                d3.select('#saveConfigBtn').text('Overwrite selected config');
                break;
        }
    }
}

var nextId = 100;

function saveConfigDB(confObj) {
    $.ajax({
        url: '/api/saveConfig.php?t=' + confType,
        async: true,
        tryCount: 0,
        retryLimit: 3,
        config: confObj,
        data: confObj,
        success: function(result) {
            var obj = result.parseJSON();
            if (typeof(obj.error) === 'undefined') {
                c[jsonIndexOf(c, 'id', this.config.id)].id = obj.id;
                origConfig = obj.id;
                if (typeof(confCookie) !== 'undefined') {
                    setCookie(confCookie, obj.id, 365);
                }
                initConfig();
            } else {
                alert(obj.error);
            }
        },
        error: function(xhr, textStatus, errorThrown, ajaxObj) {
            alert('sc1: error');
        }
    });
}

function saveConfig() {
    if (document.getElementById('configCreateRadio').checked) {
        if (document.getElementById('configCreate').value == '') {
            alert('Please give your config a name.');
            return;
        }
        if (c.map(function(d) {
                return d.name.toLowerCase()
            }).indexOf('Runs'.toLowerCase()) > -1) {
            alert('Please give your config a unique name.');
            return;
        }
        var item = c.filter(function(d) {
            return d.temp
        })[0];
        if (typeof(item) === 'undefined') {
            item = clone(c.filter(function(d) {
                return d.id == origConfig
            })[0]);
            c.push(item);
        }
        item.temp = false;
        item.name = document.getElementById('configCreate').value;
        // this needs sorting
        item.id = '1000000';
        saveConfigDB(item);
        //origConfig = item.id;
        //initConfig();
    } else {
        var selConfId = document.getElementById('configOverwrite').value;
        var selConfIndex = jsonIndexOf(c, 'id', selConfId);
        selectedConfId = c.filter(function(d, i) {
            return d.id == selConfId
        })[0];
        origConfig = selConfId;

        selectedConfId.colOrder = c[selConfIndex].colOrder;
        selectedConfId.colDisp = c[selConfIndex].colDisp;
        selectedConfId.dataOrder = c[selConfIndex].dataOrder;
        selectedConfId.filters = c[selConfIndex].filters;

        //c.splice(selConfIndex, 1);

        initConfig();
    }
    d3.select('#saveConfigModalBtn')
        .attr('disabled', 'disabled');

    $('#saveColConfigModal').modal('hide');
}

function popSaveConfig() {
    var tIndex = jsonIndexOf(c, 'temp', true);

    d3.select('#configOverwrite')
        .selectAll('option')
        .remove();

    d3.select('#configOverwrite')
        .selectAll('option')
        .data(c.filter(function(d) {
            return d.editable && !d.temp
        }))
        .enter()
        .append('option')
        .attr('value', function(d) {
            return d.id
        })
        .text(function(d) {
            return d.name
        });

    var oIndex = jsonIndexOf(c, 'id', origConfig);
    if (oIndex > -1 && c[oIndex].editable) {
        document.getElementById('configOverwrite').value = c[oIndex].id;
        d3.select('#configOverwriteRadio').attr('checked', 'checked');
        setConfigSaveOptions(d3.select('#configOverwriteRadio')[0][0]);
    } else {
        document.getElementById('configOverwrite').selectedIndex = 0;
        d3.select('#configCreateRadio').attr('checked', 'checked');
        setConfigSaveOptions(d3.select('#configCreateRadio')[0][0]);
    }
}

function setConfigOptions() {
    var cnf = d3.select('#colCfgOpt tbody');

    cnf.selectAll('tr').remove();

    var rows = cnf.selectAll('tr')
        .data(columns.sort(function(a, b) {
            return (a.displayOrder == -1 ? 1000 : a.displayOrder) - (b.displayOrder == -1 ? 1000 : b.displayOrder)
        }))
        .enter()
        .append('tr');

    rows.append('td')
        .html(function(d) {
            return d.title.replace(/\<br\/\>/g, ' ').replace(' ', '&nbsp;')
        });

    var dispSels = rows.append('td')
        .append('select')
        .on('change', function(d) {
            d.respDisp = this.options[this.selectedIndex].value;
            theadChanginged = true;
            cacheConfig();
            dataLoaded();
        });

    dispSels.append('option')
        .attr('value', 7)
        .text('Hidden All')
        .attr('selected', function(d) {
            return (d.respDisp == '7' ? 'selected' : null)
        });
    dispSels.append('option')
        .attr('value', 0)
        .text('Visible All')
        .attr('selected', function(d) {
            return (d.respDisp == '0' ? 'selected' : null)
        });
    dispSels.append('option')
        .attr('value', 1)
        .text('Visible Large')
        .attr('selected', function(d) {
            return (d.respDisp == '1' ? 'selected' : null)
        });
    dispSels.append('option')
        .attr('value', 2)
        .text('Visible Medium')
        .attr('selected', function(d) {
            return (d.respDisp == '2' ? 'selected' : null)
        });
    dispSels.append('option')
        .attr('value', 3)
        .text('Visible Small')
        .attr('selected', function(d) {
            return (d.respDisp == '3' ? 'selected' : null)
        });
    dispSels.append('option')
        .attr('value', 4)
        .text('Hidden Large')
        .attr('selected', function(d) {
            return (d.respDisp == '4' ? 'selected' : null)
        });
    dispSels.append('option')
        .attr('value', 5)
        .text('Hidden Medium')
        .attr('selected', function(d) {
            return (d.respDisp == '5' ? 'selected' : null)
        });
    dispSels.append('option')
        .attr('value', 6)
        .text('Hidden Small')
        .attr('selected', function(d) {
            return (d.respDisp == '6' ? 'selected' : null)
        });

    var funcGrps = rows.append('td')
        .append('div')
        .classed('btn-group', true)
        .classed('btn-group-xs', true);

    funcGrps.append('button')
        .attr('type', 'button')
        .classed('btn', true)
        .classed('btn-primary', true)
        .classed('colConfigBtn', true)
        .attr('disabled', function(d) {
            return (d.displayOrder == 0 ? 'disabled' : null)
        })
        .html('<i class="icon-arrow-up icon-white" style="line-height:10px;display:block"></i>')
        .on('click', function(d) {
            var oldOrder = d.displayOrder;
            var newOrder = oldOrder - 1;
            var oldIndex = jsonIndexOf(columns, 'displayOrder', newOrder);
            if (oldIndex > -1) {
                columns[oldIndex].displayOrder = oldOrder;
            }
            d.displayOrder = newOrder;
            theadChanginged = true;
            cacheConfig();
            dataLoaded();
            setConfigOptions();
        });

    funcGrps.append('button')
        .attr('type', 'button')
        .classed('btn', true)
        .classed('btn-primary', true)
        .classed('colConfigBtn', true)
        .attr('disabled', function(d) {
            return (d.displayOrder == (d3.max(columns.map(function(d) {
                return d.displayOrder
            }))) ? 'disabled' : null)
        })
        .html('<i class="icon-arrow-down icon-white" style="line-height:10px;display:block"></i>')
        .on('click', function(d) {
            var oldOrder = d.displayOrder;
            var newOrder = oldOrder + 1;
            var oldIndex = jsonIndexOf(columns, 'displayOrder', newOrder);
            if (oldIndex > -1) {
                columns[oldIndex].displayOrder = oldOrder;
            }
            d.displayOrder = newOrder;
            theadChanginged = true;
            cacheConfig();
            dataLoaded();
            setConfigOptions();
        });
}

var geoCounter = 0;

function init() {
    //initOther();
    initConfig();
    initChartOptions();
    /*columns.forEach(function(d, i) {
      if (d.filterActive == 1) {
        initFilter(d);
      }
    });*/
    setFilterActive();

    if (getCookie(cpref + 't') != null) {
        d3.select('#viewTableCheckBox').classed('active', getCookie(cpref + 't') == 'true');
        tableShownChecked = getCookie(cpref + 't') == 'true';
    }
    if (typeof(embed) !== 'undefined' && embed && typeof(initMap) !== 'undefined') {
        initMap('', true, false);
        mapShownChecked = true;
    } else {
        if (getCookie(cpref + 'm') != null) {
            d3.select('#viewMapCheckBox').classed('active', getCookie(cpref + 'm') == 'true');
            mapShownChecked = getCookie(cpref + 'm') == 'true';
            if (mapShownChecked) {
                initMap('', true, true);
            }
        }
    }
    if (getCookie(cpref + 'c') != null) {
        d3.select('#viewChartCheckBox').classed('active', getCookie(cpref + 'c') == 'true');
        chartShownChecked = getCookie(cpref + 'c') == 'true';
    }
    if (getCookie(cpref + 'p') != null) {
        d3.select('#viewPhotosCheckBox').classed('active', getCookie(cpref + 'p') == 'true');
        photosShownChecked = getCookie(cpref + 'p') == 'true';
    }

    setMainControls();
    setOrder(a, true);
    dataLoaded(undefined, true);
    updateActiveFilters();
    updateAvailableFilters();
    writeURL();

    if (cpref != 'sd' && (typeof(routePage) === 'undefined' || routePage == 1)) {
        setTimeout(function() {
            $('#collapseFilter').collapse('hide');
            var colBtn = $('#filtersExpander')[0];
            setCollapseState(colBtn);
            d3.select(colBtn).classed('collapsed', true);
        }, 2000);
    }
}

function orderColumns() {
    columns = columns.sort(function(a, b) {
        return a - b;
    });
}

function setOrder(data, resetOffset) {
    if (resetOffset) {
        offset = 0;
    }
    d3.selectAll('#dataTable th, #st-head tbale th')
        .classed('ascOrder', false)
        .classed('descOrder', false);

    if (orderCol == null) {
        orderCol = columns[0];
    }

    d3.select('#' + orderCol.field + 'TH')
        .classed(orderDir + 'Order', true);

    data.sort(function(a, b) {
        var av = 0;
        var bv = 0;
        var tDir = orderDir;
        switch (orderCol.type) {
            case 'num':
            case 'geo':
            case 'smlPace':
            case 'lrgPace':
                av = (a[orderCol.field] == 0 && orderCol.hideZeros == 1 ? (orderDir == 'asc' ? 1 : -1) * 100000000 : parseFloat(a[orderCol.field]));
                bv = (b[orderCol.field] == 0 && orderCol.hideZeros == 1 ? (orderDir == 'asc' ? 1 : -1) * 100000000 : parseFloat(b[orderCol.field]));

                if ((orderCol.field == 'r' || orderCol.field == 'previous') && av == bv) {
                    av += 1 - parseInt(a['tac']) / 1000000;
                    bv += 1 - parseInt(b['tac']) / 1000000;
                }
                if ((orderCol.field == 'op') && av == bv) {
                    av += 1 - parseInt(a['r']) / 1000000;
                    bv += 1 - parseInt(b['r']) / 1000000;
                }
                break;
            case 'date':
                av = parseDateTime(a[orderCol.field]).getTime();
                bv = parseDateTime(b[orderCol.field]).getTime();

                if (orderCol.field == 'placingDate' && av == bv) {
                    av = parseInt(a['r']) - a['tac'] / 1000000;
                    bv = parseInt(b['r']) - b['tac'] / 1000000;

                    tDir = (tDir == 'asc' ? 'desc' : 'asc');
                }
                break;
            case 'bool':
                av = a[orderCol.field];
                bv = b[orderCol.field];
                break;
            case 'segPRs':
                av = a[orderCol.field + 'Ord1'];
                bv = b[orderCol.field + 'Ord1'];
                if (tDir == 'asc') {
                    if (av < bv) {
                        return -1;
                    }
                    if (av > bv) {
                        return 1;
                    }
                } else {
                    if (av < bv) {
                        return 1;
                    }
                    if (av > bv) {
                        return -1;
                    }
                }
                return 0;
                break;
            default:
                av = (a[orderCol.field] == null ? '' : a[orderCol.field + (orderCol.field == 'dow' ? 'Ord1' : '')]).toString().toLowerCase();
                bv = (b[orderCol.field] == null ? '' : b[orderCol.field + (orderCol.field == 'dow' ? 'Ord1' : '')]).toString().toLowerCase();

                if (orderCol.field == 'climbCat' && av == bv) {
                    av = parseInt(a['ed']);
                    bv = parseInt(b['ed']);

                    tDir = (tDir == 'asc' ? 'desc' : 'asc');
                }
                if (tDir == 'asc') {
                    if (av < bv) {
                        return -1;
                    }
                    if (av > bv) {
                        return 1;
                    }
                } else {
                    if (av < bv) {
                        return 1;
                    }
                    if (av > bv) {
                        return -1;
                    }
                }
                if (av == bv && typeof(a.seq) !== 'undefined') {
                    if (tDir == 'asc') {
                        return a.seq - b.seq;
                    } else {
                        return b.seq - a.seq;
                    }
                }
                return 0;
                break;
        }
        if (av == bv && typeof(a.seq) !== 'undefined') {
            if (tDir == 'asc') {
                return a.seq - b.seq;
            } else {
                return b.seq - a.seq;
            }
        }
        if (tDir == 'asc') {
            av = (isNaN(av) ? 1000000 : av);
            bv = (isNaN(bv) ? 1000000 : bv);
            return av - bv;
        } else {
            av = (isNaN(av) ? 0 : av);
            bv = (isNaN(bv) ? 0 : bv);
            return bv - av;
        }

    });
}

function applyFilters(arr) {
    if (filterActiveColumns.length == 0) {
        return arr;
    }
    var i;
    var retArr = new Array();
    filterActiveColumns.forEach(function(d) {
        if (d.type == 'date' || d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'num' || d.type == 'geo') {
            d.filteredData = new Array();

            for (j = 0; j <= filterCols + 1; j++) {
                d.filteredData[j] = {
                    id: j,
                    min: j * d.timeStep,
                    max: (j + 1) * d.timeStep,
                    count: 0
                };
            }
        }
    });

    for (i = 0; i < arr.length; i++) {
        arr[i].shown = 1;
        var inc = true;

        filterActiveColumns.filter(function(d, i) {
            return (!(d.type == 'date' || d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'num' || d.type == 'geo') ||
                (typeof(d.filterMin) !== 'undefined' && (d.filterMin > d.min || d.filterMax < d.max)));
        }).forEach(function(d, j) {
            if (inc && (d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'num' || d.type == 'geo')) {
                if (typeof(arr[i][d.field]) === 'undefined') {
                    inc = false;
                    arr[i].shown = 0;
                } else {
                    if (d.display == 'arrow') {
                        if (!((arr[i][d.field] - 360 >= d.filterMin && arr[i][d.field] - 360 <= d.filterMax) ||
                                (arr[i][d.field] >= d.filterMin && arr[i][d.field] <= d.filterMax) ||
                                (arr[i][d.field] + 360 >= d.filterMin && arr[i][d.field] + 360 <= d.filterMax))) {
                            inc = false;
                            arr[i].shown = 0;
                        }
                    } else {
                        if (arr[i][d.field] < d.filterMin || arr[i][d.field] > d.filterMax) {
                            inc = false;
                            arr[i].shown = 0;
                        }
                    }
                }
            }
            if (inc && d.type == 'date') {
                if ((arr[i][d.field + 'Ms'] < d.filterMin) || (arr[i][d.field + 'Ms'] > d.filterMax)) {
                    inc = false;
                    arr[i].shown = 0;
                }
            }
            if (inc && d.type == 'map') {
                if (typeof(arr[i].map) === 'undefined' ||
                    arr[i].map == null ||
                    arr[i].startLat == 0 ||
                    arr[i].minLat == 10000 ||
                    arr[i].minLat == 10000000000 ||
                    arr[i].startLng == 0 ||
                    arr[i].minLat < d.minLat ||
                    arr[i].maxLat > d.maxLat ||
                    arr[i].minLng < d.minLng ||
                    arr[i].maxLng > d.maxLng) {
                    inc = false;
                    arr[i].shown = 0;
                }
            }
            if (inc && d.type == 'list') {
                if (typeof(d.filterSelectedItems) !== 'undefined' && d.filterSelectedItems.indexOf(arr[i][d.field].toString()) == -1) {
                    inc = false;
                    arr[i].shown = 0;
                }
            }
            if (inc && d.type == 'text') {
                if (d.field == 'actSegId') {
                    if (!teffs[arr[i].i] || teffs[arr[i].i].map(function(d) { return d.si }).indexOf(+d.filterVal) == -1) {
                        inc = false;
                        arr[i].shown = 0;
                    }
                } else {
                    if (typeof(d.filterVal) !== 'undefined' && (arr[i][d.field] == null || !arr[i][d.field].toString().fullTextSearch(d.filterVal))) {
                        inc = false;
                        arr[i].shown = 0;
                    }
                }
            }
        });

        filterActiveColumns.forEach(function(d, j) {
            if (arr[i].shown == 1) {
                if (arr[i][d.field + 'Col'] >= 0) {
                    d.filteredData[arr[i][d.field + 'Col']].count++;
                }
            }
        });

        if (inc) {
            retArr.push(arr[i])
        }
    }

    filterFilters();

    return retArr;
}

var chartXCol, chartYCols, chartData;

function getVal(d, col) {
    if (col.display == 'date') {
        return d[col.field + 'Ms'];
    }
    /*if (col.type=='lrgPace' || col.type=='averagePaceLrg')
    {
      return d[col.field+'Lrg'];
    }
    if (col.type=='smlPace' || col.type=='averagePaceSml')
    {
      return d[col.field+'Sml'];
    }*/
    return d[col.field];
}

function getChartData() {
    var retData = [];
    var chartData = applyFilters(a);
    var chartHueScale;

    if (chartXCol == null) {
        chartXCol = columns[jsonIndexOf(columns, 'id', chartDefX)];
    }

    var l_orderCol = orderCol;
    var l_orderDir = orderDir;

    orderCol = chartXCol;
    orderDir = 'asc';

    setOrder(chartData, false);

    orderCol = l_orderCol;
    orderDir = l_orderDir;

    if (chartYCols == null) {
        chartYCols = [];
        chartYCols.push({
            "y": columns[jsonIndexOf(columns, 'id', chartDefY)],
            "z": columns[jsonIndexOf(columns, 'id', chartDefZ)],
            "c": columns[jsonIndexOf(columns, 'id', chartDefC)]
        });
    }

    chartData = chartData.filter(function(d) { return d[chartYCols[0].y.field] != null && d[chartYCols[0].y.field] != null });

    for (var i = 0; i < chartYCols.length; i++) {
        if (chartYCols[i].c != null && chartYCols[i].c.type != 'list') {
            chartHueScale = d3.scale.linear()
                .domain(d3.extent(chartData.filter(function(d, j) {
                    return chartYCols[i].c.hideZeros == 0 || d[chartYCols[i].c.field] > 0
                }).map(function(d) {
                    return chartYCols[i].c.type == 'date' ? new Date(d[chartYCols[i].c.field]).getTime() : parseFloat(d[chartYCols[i].c.field]);
                })))
                .range([100, 0]);
        }
        if (chartYCols[i].c != null && chartYCols[i].c.type == 'list') {
            var catArr = chartData.map(function(d) {
                return d[chartYCols[i].c.field];
            }).getUnique().sort();
            if (catArr.length <= 10) {
                chartHueScale = d3.scale.category10()
                    .domain(catArr);
            } else {
                chartHueScale = d3.scale.category20()
                    .domain(catArr);
            }
        }

        var series = [];
        var lastXVal = 0;
        var yRunningTotal = 0;
        var previousX = '';
        var j, x, y, z, c;
        for (j = 0; j < chartData.length; j++) {
            var hasXChanged = true;
            c = 1;
            switch (chartXCol.display) {
                case 'date':
                    x = getVal(chartData[j], chartXCol);
                    if (chartTypeVal == 1) {
                        while (Math.max(lastXVal, getVal(chartData[0], chartXCol)) < x) {
                            lastXVal += 86400000;
                            series.push({
                                "series": i,
                                "x": lastXVal,
                                "y": 0
                            });
                        }
                        lastXVal = x;
                    }
                    break;
                case 'list':
                    var v = getVal(chartData[j], chartXCol);
                    if (typeof(v) === 'number') {
                        v = (v == 1 ? 'Yes' : 'No')
                    }
                    x = v.replace('&nbsp;', '');
                    break;
                default:
                    x = parseFloat(getVal(chartData[j], chartXCol));
                    x = (isNaN(x) ? 0 : x);
            }
            if (chartYCols[i].c == null) {
                c = null;
            } else {
                if (chartYCols[i].c.type == 'list') {
                    c = chartHueScale(getVal(chartData[j], chartYCols[i].c));
                } else {
                    c = 'hsla(' + f1dp(chartHueScale(parseFloat(getVal(chartData[j], chartYCols[i].c)))) + ',' + (chartYCols[i].c.hideZeros == 0 || chartData[j][chartYCols[i].c.field] > 0 ? '10' : '0') + '0%,35%,1)';
                }
            }
            if (chartYCols[i].z == null) {
                z = 1;
            } else {
                z = parseFloat(getVal(chartData[j], chartYCols[i].z));
                z = (isNaN(z) ? 0 : z);
            }
            if (chartXCol.type == 'list' || chartTypeVal == 1) {
                if (x != previousX) {
                    if (previousX != '') {
                        series.push({
                            "series": i,
                            "id": chartData[j].i,
                            "x": previousX,
                            "y": yRunningTotal,
                            "size": z,
                            "c": c
                        });
                    }

                    yRunningTotal = 0;
                    previousX = x;
                } else {
                    hasXChanged = false;
                }

                yRunningTotal += parseFloat(getVal(chartData[j], chartYCols[i].y));
            } else {
                if (typeof(x) !== 'undefined' && getVal(chartData[j], chartYCols[i].y) != '' && typeof(z) !== 'undefined') {
                    series.push({
                        "series": i,
                        "id": chartData[j].i,
                        "x": x,
                        "y": parseFloat(getVal(chartData[j], chartYCols[i].y)),
                        "size": z,
                        "c": c
                    });
                }
            }
        }
        if (chartXCol.type == 'list' || chartTypeVal == 1) {
            series.push({
                "series": i,
                "id": chartData[j - 1].i,
                "x": previousX,
                "y": yRunningTotal,
                "size": z,
                "c": c
            });
        }
        retData.push({
            "key": chartXCol.desc + ' by ' + chartYCols[i].y.desc + (chartYCols[i].z != null ? ' by ' + chartYCols[i].z.desc : '') + (chartYCols[i].c != null ? ' by ' + chartYCols[i].c.desc : ''),
            "values": series
        });
    }

    return retData;
}

function formatVal(d, display) {
    switch (display) {
        case 'int':
        case 'arrow':
            return d3.format(',.0f')(d);
            break;
        case '1dp':
            return d3.format(',.1f')(d);
            break;
        case '2dp':
            return d3.format(',.2f')(d);
            break;
        case '3dp':
            return d3.format(',.3f')(d);
            break;
        case 'date':
            return d3.time.format('%d %b %y')(parseDateTime(d));
            break;
        case 'lLen':
            return d3.format(',.2f')(d * lrgLenMult) + ' ' + lrgLenUnit;
            break;
        case 'vlLen':
            return d3.format(',.3f')(d * lrgLenMult) + ' ' + lrgLenUnit;
            break;
        case 'sLen':
            return d3.format(',.0f')(d * smlLenMult) + ' ' + smlLenUnit;
            break;
        case 'speed':
            return d3.format(',.1f')(d * speedMult) + ' ' + speedUnit;
            break;
        case 'lTime':
            return d.toDayHrMinSec2();
            break;
        case 'sTime':
            return d.toDayHrMinSec2();
            break;
        case 'yrCumCount':
            return d3.format(',.0f')(d);
            break;
        case 'list':
            switch (d) {
                case 1:
                    return 'Yes';
                    break;
                case 0:
                    return 'No';
                    break;
            }
    }
    return d;
}

var nvd3Int;

function drawChart() {
    // can have multiple y series i sharing x axis
    // must have their own scales
    // xaxis
    if (typeof(nv) === 'undefined') return;

    writeURL();

    if (a.length == 0) {
        return;
    }

    nvd3Int = window.setInterval(function() {
        if (d3.selectAll('.nvtooltip')[0].length > 1) d3.select('.nvtooltip').remove()
    }, 1000);

    nv.addGraph(function() {
        var chart;
        var chartDispatch;

        if (a.length == 0) {
            return chart;
        }
        var data = getChartData();

        switch (chartTypeVal) {
            case 0:
                chart = nv.models.scatterChart();
                chartDispatch = chart.scatter.dispatch;
                /*chart.tooltip.contentGenerator(function(data) {
                    var h = '<div><div style="margin:6px 6px 0px 6px"><strong class="x-value">' + fDate(new Date(data.point.x)) + '</strong></div>';

                    h += '<div style="margin:6px 6px 0px 6px;white-space:normal;font-weight:bold;width:200px;font-size:0.8em">' + data.point.d.an + '</div>';
                    h += '<table><tbody>';
                    h += '<tr><td class="legend-color-guide"><div style="background-color:' + data.point.color + '"></div></td>';
                    h += '<td class="key">' + data.series[0].key + '</td>';
                    h += '<td class="value">' + tcDataFormat(data.point.y) + '</td>';
                    h += '</tbody></table></div>';
                    return h;
                });*/
                break;
            case 1:
                chart = nv.models.multiBarChart();
                chartDispatch = chart.dispatch;
                break;
            case 2:
                chart = nv.models.lineChart();
                chartDispatch = chart.dispatch;
                break;
        }
        /*.showDistX(true)
        .showDistY(true)
        .useVoronoi(true)
        .interpolate('step-after');*/
        if (chartTypeVal == 0) {
            /*chart.color(d3.scale.category10().range())
            .tooltip.contentGenerator(function(data) {
              return 'this is my custom content';
            };*/
            chart.color(d3.scale.category10().range())
                .tooltipContent(function(obj) {
                    var html = '';
                    var i = jsonIndexOf(a, 'i', obj.point.id);
                    if (cpref != 'a') {
                        if (cpref != 'e') {
                            if (cpref != 'r') {
                                html += '<h3>' + a[i].sn + '</h3>';
                            } else {
                                html += '<h3>' + a[i].name + '</h3>';
                            }
                        } else {
                            html += '<h3>' + a[i].s_sn + '</h3>';
                        }
                    } else {
                        html += '<h3>' + a[i].an + '</h3>';
                    }
                    html += '<p>' + chartXCol.desc + ': ' + (a[i][chartXCol.field] !== 'undefined' ? formatVal(a[i][chartXCol.field], chartXCol.display) : 'n/a') + '</p>';
                    html += '<p>' + chartYCols[0].y.desc + ': ' + (a[i][chartYCols[0].y.field] !== 'undefined' ? formatVal(a[i][chartYCols[0].y.field], chartYCols[0].y.display) : 'n/a') + '</p>';
                    if (chartYCols[0].z != null) {
                        html += '<p>' + chartYCols[0].z.desc + ' (size): ' + (a[i][chartYCols[0].z.field] !== 'undefined' ? formatVal(a[i][chartYCols[0].z.field], chartYCols[0].z.display) : 'n/a') + '</p>';
                    }
                    if (chartYCols[0].c != null) {
                        html += '<p>' + chartYCols[0].c.desc + ' (colour): ' + (a[i][chartYCols[0].c.field] !== 'undefined' ? formatVal(a[i][chartYCols[0].c.field], chartYCols[0].c.display) : 'n/a') + '</p>';
                    }
                    return html;
                });
        }

        if (typeof(chartDispatch.elementClick) !== 'undefined') {
            chartDispatch.on('elementClick.veloviewer', function(e) {
                if (cpref != 'a') {
                    if (cpref != 'e') {
                        if (cpref != 'r') {
                            window.open('/segments/' + e.point.id, '_blank');
                        } else {
                            window.open('/routes/' + e.point.id, '_blank');
                        }
                    } else {
                        window.open('http://www.strava.com/segment_efforts/' + e.point.id, '_blank');
                    }
                } else {
                    window.open('/athletes/' + (typeof(contextAthleteId) === 'undefined' ? athleteId : contextAthleteId) + '/activities/' + e.point.id, '_blank');
                }
            });
        }
        if (typeof(chartDispatch.elementMouseover) !== 'undefined') {
            chartDispatch.on('elementMouseover.veloviewer', function(e) {
                d3.select('#tr' + e.point.id)
                    .classed('info', true);
                d3.selectAll('#chartItem' + e.point.id)
                    .classed('hover', true);
                /*if (mapShownChecked && typeof(d.mapFeature) !== 'undefined') {
                  d = a[jsonIndexOf(a, 'i', e.point.id)];
                  lineLayer.removeFeatures(d.mapFeature);
                  lineLayer.addFeatures([d.mapFeature]);
                  d.mapFeature.style = lineHoverStyle;
                  lineLayer.redraw();
                }*/
            });
            chartDispatch.on('elementMouseout.veloviewer', function(e) {
                d3.select('#tr' + e.point.id)
                    .classed('info', false);
                d3.selectAll('#chartItem' + e.point.id)
                    .classed('hover', false);
                /*if (mapShownChecked && typeof(d.mapFeature) !== 'undefined') {
                  d = a[jsonIndexOf(a, 'i', e.point.id)];
                  d.mapFeature.style = lineStyle;
                  lineLayer.redraw();
                }*/
            });
        }

        switch (chartXCol.display) {
            case 'int':
            case 'arrow':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.format(',.0f')(d)
                    });
                break;
            case '1dp':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.format(',.1f')(d)
                    });
                break;
            case '2dp':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.format(',.2f')(d)
                    });
                break;
            case '3dp':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.format(',.3f')(d)
                    });
                break;
            case 'date':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.time.format('%d %b %y')(parseDateTime(d))
                    });
                break;
            case 'lLen':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.format(',.0f')(d * lrgLenMult)
                    });
                break;
            case 'vlLen':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.format(',.2f')(d * lrgLenMult)
                    });
                break;
            case 'sLen':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.format(',.0f')(d * smlLenMult)
                    });
                break;
            case 'speed':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.format(',.1f')(d * speedMult)
                    });
                break;
            case 'lTime':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d.toDayHrMinSec2();
                    });
                break;
            case 'sTime':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d.toDayHrMinSec2();
                    });
                break;
            case 'yrCumCount':
                chart.xAxis
                    .tickFormat(function(d) {
                        return d3.format(',.0f')(d)
                    });
                break;
        }

        switch (chartYCols[0].y.display) {
            case 'int':
            case 'arrow':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.format(',.0f')(d)
                    });
                break;
            case '1dp':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.format(',.1f')(d)
                    });
                break;
            case '2dp':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.format(',.2f')(d)
                    });
                break;
            case '3dp':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.format(',.3f')(d)
                    });
                break;
            case 'date':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.time.format('%d %b %y')(parseDateTime(d))
                    });
                break;
            case 'lLen':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.format(',.0f')(d * lrgLenMult)
                    });
                break;
            case 'vlLen':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.format(',.2f')(d * lrgLenMult)
                    });
                break;
            case 'sLen':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.format(',.0f')(d * smlLenMult)
                    });
                break;
            case 'speed':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.format(',.1f')(d * speedMult)
                    });
                break;
            case 'lTime':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d.toDayHrMinSec2();
                    });
                break;
            case 'sTime':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d.toDayHrMinSec2();
                    });
                break;
            case 'yrCumCount':
                chart.yAxis
                    .tickFormat(function(d) {
                        return d3.format(',.0f')(d)
                    });
                break;
        }

        chart.xAxis.axisLabel(chartXCol.desc);
        chart.yAxis.axisLabel(chartYCols[0].y.desc);

        if (chartXCol.type != 'date' && typeof(chart.forceX) !== 'undefined') {
            //chart.forceX([0]);
        }
        //chart.forceY([0]);

        chart.duration(0);

        d3.select('#chart svg')
            .datum(data)
            .transition().duration(0)
            .call(chart);

        nv.utils.windowResize(function() {
            chart.update();
            if (chartTypeVal == 1) {
                d3.selectAll('#chart .nv-x .tick.zero text')
                    .style('opacity', null)
                    .attr("y", 10)
                    .attr("x", 0)
                    .attr("dy", ".35em")
                    .attr("transform", "rotate(-20)")
                    .style("text-anchor", "end");
                d3.selectAll('#chart .nv-x .nv-axislabel').each(function() {
                    var o = d3.select(this);
                    o.attr('text-anchor', 'end').attr('x', this.parentElement.getBoundingClientRect().width - this.getBoundingClientRect().width * 1);
                });
            }
        });

        d3.selectAll('#chart .nv-series-0 path')
            .style('fill', function(d) {
                return d[0].c
            }).style('stroke', function(d) {
                return d[0].c
            });

        if (chartTypeVal == 1) {
            d3.selectAll('#chart .nv-x .tick.zero text')
                .style('opacity', null)
                .attr("y", 10)
                .attr("x", 0)
                .attr("dy", ".35em")
                .attr("transform", "rotate(-20)")
                .style("text-anchor", "end");
            d3.selectAll('#chart .nv-x .nv-axislabel').each(function() {
                var o = d3.select(this);
                o.attr('text-anchor', 'end').attr('x', this.parentElement.getBoundingClientRect().width - this.getBoundingClientRect().width * 1);
            });
        }

        return chart;
    });
}

var photoObserver;
var photoActivePopup = null;
function photoClearPopups() {
  if (photoActivePopup != null) {
    $(photoActivePopup).popover('hide');
    d3.selectAll('.photo' + photoActivePopup.__data__.act.ai)
        .style('background', null);
    photoActivePopup = null;
  } else {
    if (d3.event.srcElement.localName == 'img') {
      photoActivePopup = d3.event.srcElement;
      $(d3.event.srcElement).popover('show');
      d3.selectAll('.photo' + photoActivePopup.__data__.act.ai)
          .style('background', '#FF032E');
    }
  }
}

function photoLoad(div, d) {
  div.append('img')
  .style({
    'border': '0.5px solid #bbb',
    'height': '75px',
    'width': (75 * d.x / d.y) + 'px'
  }).each(function(d) {
    var mw, mh = Math.min(document.getElementById('photosContainer').getBoundingClientRect().height - 75, 380);
    if (mh * d.x / d.y > 380 || mh * d.x / d.y > document.getElementById('photosContainer').getBoundingClientRect().width - 50) {
      mw = Math.min(document.getElementById('photosContainer').getBoundingClientRect().width - 50, 380);
      mh = mw * d.y / d.x;
    } else {
      mw = mh * d.x / d.y;
    }
    $(this).popover({
      placement: function(context, source) {
        var d = source.__data__;
        var mw, mh = Math.min(document.getElementById('photosContainer').getBoundingClientRect().height - 75, 380);
        if (mh * d.x / d.y > 380 || mh * d.x / d.y > document.getElementById('photosContainer').getBoundingClientRect().width - 50) {
          mw = Math.min(document.getElementById('photosContainer').getBoundingClientRect().width - 50, 380);
          mh = mw * d.y / d.x;
        } else {
          mw = mh * d.x / d.y;
        }
        var distFromTop = $(source).offset().top;
        return distFromTop < window.innerHeight / 2 ? "bottom" : "top";
      },
      html: true,
      trigger: 'manual',
      title: '<a target="_blank" href="' + d.u2048 + '"><img src="' + d.url + '" width="' + mw + '" height="' + mh + '" style="max-width: 100%;height: auto;" /></a>',
      content: '<p style="max-width:' + mw + 'px">' + d.caption + '</p>',
      container: 'body'
    });
  }).attr('src', isRetinaDisplay() ? d.u256 : d.u128);
}

function drawPhotos() {
  if (liveData) {
    if (contextAthleteId != loggedInAthleteId) {
      d3.select('#photosContainer').style({
        'background': '#fff',
        'border': '0.5px solid #bbb',
        'margin-top': '4px',
        'padding': '10px'
      });
      if (loggedInAthleteId < 0) {
        d3.select('#photosContainer').html('Log in to view your own photos');
      } else {
        d3.select('#photosContainer').html('You can only view your own photos.');
      }
      return;
    }

    if (typeof(photoObserver) !== 'undefined') {
      photoObserver.disconnect();
    } else {
      if (typeof(IntersectionObserver)!=='undefined') {
        photoObserver = new IntersectionObserver(function(entries) {
          entries.filter(function(d){return d.isIntersecting}).forEach(function(d){
            if (d.target.childElementCount == 0) {
              photoLoad(d3.select(d.target), d.target.__data__);
            }
            //d.target.src = isRetinaDisplay() ? d.target.__data__.u256 : d.target.__data__.u128;
          });
        }, { threshold: [0],  delay: 250 });
      }
    }

    var data = d3.merge(liveData.filter(function(d){return d.photosCount > 0}).map(function(d) {
      return d.photos
    }));

    data.push({i: -10000});

    var pDivs = d3.select('#photosContainer').style({
      'overflow-y': 'auto',
      'background': '#fff',
      'border': '0.5px solid #bbb',
      'margin-top': '4px'
    }).on('click', function(){
      photoClearPopups()
    }).on('scroll', function(){
      photoClearPopups()
    }).selectAll('div').data(data, function(d) {
      return d.i
    });

    pDivs.exit().remove();

    pDivs.enter().append('div').each(function(d) {
      var div = d3.select(this);
      if (d.i != -10000) {
        div.attr('class', function(d) {
            return 'photo' + d.act.i
          }).style({
            'display': 'inline-block',
            'padding': '2px',
            'height': '81px',
            'width': ((75 * d.x / d.y) + 6) + 'px' // add padding x2 + border x2 = 2x2+1*2 = 6 px
          });
        if (typeof(photoObserver) === 'undefined') {
          photoLoad(div, d);
        }
      } else {
        div.classed('photoInfoDiv', true);
      }
    });
    pDivs.order();

    d3.select('.photoInfoDiv').html('<h3 style="margin-left: 10px">'+(data.length == 1 ? 'No photos available for current filters:' : 'If some photos are missing then please see:' ) + ' <a target="_blank" href="https://blog.veloviewer.com/viewing-all-of-your-strava-photos-on-veloviewer/">more info</a></h3>');

    if (typeof(photoObserver) !== 'undefined') {
      pDivs.each(function(d){
        photoObserver.observe(this);
      });
    }
  }
}

function drawPhotosScroll(aId) {
  if (isScrolledIntoView(document.querySelector("#photosContainer"))) {
    if (d3.select('.photo'+aId).node() != null) {
      d3.select('.photo'+aId).node().scrollIntoView({behavior: "auto", block: "center"});
    }
  }
}

function dataLoaded(noMapZoom, noWriteUrl) {
    noMapZoom = typeof(noMapZoom) === 'undefined' ? false : noMapZoom;
    noWriteUrl = typeof(noWriteUrl) === 'undefined' ? false : noWriteUrl;
    var startTime = new Date();

    setTimeout(function() {
        d3.select('#loadingMessage').style('display', 'inline');
        d3.select('#loadingMessage').text('Refreshing filters...');
    }, 1);

    if (theadChanginged) {
        d3.select("#dataTable colgroup").selectAll("col").remove();
        d3.select("#dataTable thead").selectAll("th").remove();
    }
    d3.select("#dataTable tbody").selectAll("tr").remove();
    d3.select('.pagination ul').selectAll("li").remove();

    liveData = applyFilters(a);
    liveDataObj = {};
    liveData.forEach(function(d, i) {
        liveDataObj[a.i] = i;
    });

    if (cpref == 'a') {
        var ldf = liveData.filter(function(d) { return d.map });
        d3.select('#sendToWheelBtn').remove();
        d3.select('#tableContainer .pager').insert('a', '#actualRowCount + *').classed('btn btn-primary btn-mini', true).text('View in wheel')
            .attr({
                'id': 'sendToWheelBtn',
                'target': '_blank',
                'href': ldf.length <= 300 ? '/athlete/' + contextAthleteId + '/wheel?ids=' + ldf.map(function(d) { return d.i }).sort(d3.ascending).join() : 'javascript:alert("Max of 300 activities that include map data (' + ldf.length + ' currently selected). Please increase your filtering.")'
            });
    }

    if (typeof(showExplorerTiles) !== 'undefined' && (cpref == 'a' || cpref == 'r')) {
        window.setTimeout(showExplorerTiles, 10);
    }

    columns = columns.sort(function(a, b) {
        return a.displayOrder - b.displayOrder
    });

    if (theadChanginged) {
        d3.select('#dataTable colgroup')
            .selectAll('col')
            .data(columns.filter(function(d, i) {
                return d.displayOrder > -1;
            }))
            .enter()
            .append('col')
            .style('width', function(d) {
                return d.width
            })
            .style('min-width', function(d) {
                return d.width
            })
            .attr('class', function(d) {
                return d.class
            })
            .classed('visible-desktop', function(d) {
                return d.respDisp == '1'
            })
            .classed('visible-tablet', function(d) {
                return d.respDisp == '2'
            })
            .classed('visible-phone', function(d) {
                return d.respDisp == '3'
            })
            .classed('hidden-desktop', function(d) {
                return d.respDisp == '4'
            })
            .classed('hidden-tablet', function(d) {
                return d.respDisp == '5'
            })
            .classed('hidden-phone', function(d) {
                return d.respDisp == '6'
            })
            .style('display', function(d) {
                return (d.respDisp == '7' ? 'none' : null)
            });

        d3.select('#dataTable thead tr')
            .selectAll('th')
            .data(columns.filter(function(d, i) {
                return d.displayOrder > -1;
            }))
            .enter()
            .append('th')
            .style('min-width', function(d) {
                return (d.width == 'auto' ? null : d.width);
            })
            .attr('class', function(d) {
                return d.class
            })
            .classed('ar', function(d) {
                return (d.type == 'num' || d.type == 'geo' ? true : false)
            })
            .classed(orderDir + 'Order', function(d) {
                return d.field == orderCol.field;
            })
            .classed('visible-desktop', function(d) {
                return d.respDisp == '1'
            })
            .classed('visible-tablet', function(d) {
                return d.respDisp == '2'
            })
            .classed('visible-phone', function(d) {
                return d.respDisp == '3'
            })
            .classed('hidden-desktop', function(d) {
                return d.respDisp == '4'
            })
            .classed('hidden-tablet', function(d) {
                return d.respDisp == '5'
            })
            .classed('hidden-phone', function(d) {
                return d.respDisp == '6'
            })
            .style('display', function(d) {
                return (d.respDisp == '7' ? 'none' : null)
            })
            .attr('id', function(d) {
                return d.field + 'TH'
            })
            .on('mouseover', function(d) {
                var text = (typeof(d.lngDesc) !== 'undefined' && d.lngDesc != '' ? d.lngDesc : d.desc);
                if (d.type == 'num' || d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'geo') {
                    var lLiveData = liveData;
                    if (d.hideZeros == 1) {
                        lLiveData = liveData.filter(function(o) { return o[d.field] != 0 });
                    }
                    var vMax = lLiveData.length == 0 ? 0 : Math.max.apply(Math, lLiveData.map(function(o) {
                        return parseFloat(o[d.field] == null ? '0' : o[d.field]);
                    }));
                    var vMin = lLiveData.length == 0 ? 0 : Math.min.apply(Math, lLiveData.map(function(o) {
                        return parseFloat(o[d.field] == null ? '0' : o[d.field]);
                    }));
                    var vAvg = lLiveData.length == 0 ? 0 : d3.mean(lLiveData.map(function(o) {
                        return parseFloat(o[d.field] == null ? '0' : o[d.field]);
                    }));
                    var vSum = lLiveData.length == 0 ? 0 : d3.sum(lLiveData.map(function(o) {
                        return parseFloat(o[d.field] == null ? '0' : o[d.field]);
                    }));

                    switch (d.display) {
                        case 'int':
                            text += '  \nMax: ' + fInt(vMax);
                            text += '  \nMin: ' + fInt(vMin);
                            text += '  \nAvg: ' + f1dp(vAvg);
                            text += '  \nSum: ' + fInt(vSum);
                            break;
                        case '1dp':
                        case 'arrow':
                            text += '  \nMax: ' + f1dp(vMax);
                            text += '  \nMin: ' + f1dp(vMin);
                            text += '  \nAvg: ' + f1dp(vAvg);
                            text += '  \nSum: ' + f1dp(vSum);
                            break;
                        case '2dp':
                            text += '  \nMax: ' + f2dp(vMax);
                            text += '  \nMin: ' + f2dp(vMin);
                            text += '  \nAvg: ' + f2dp(vAvg);
                            text += '  \nSum: ' + f2dp(vSum);
                            break;
                        case '3dp':
                            text += '  \nMax: ' + f3dp(vMax);
                            text += '  \nMin: ' + f3dp(vMin);
                            text += '  \nAvg: ' + f3dp(vAvg);
                            text += '  \nSum: ' + f3dp(vSum);
                            break;
                        case 'sLenbyTime':
                        case 'sLen':
                            text += '  \nMax: ' + fInt(vMax * smlLenMult) + ' ' + smlLenUnit;
                            text += '  \nMin: ' + fInt(vMin * smlLenMult) + ' ' + smlLenUnit;
                            text += '  \nAvg: ' + f1dp(vAvg * smlLenMult) + ' ' + smlLenUnit;
                            text += '  \nSum: ' + f1dp(vSum * smlLenMult) + ' ' + smlLenUnit;
                            break;
                        case 'lLen':
                            if (vMax >= 0) text += '  \nMax: ' + f2dp(vMax * lrgLenMult) + ' ' + lrgLenUnit;
                            if (vMin >= 0) text += '  \nMin: ' + f2dp(vMin * lrgLenMult) + ' ' + lrgLenUnit;
                            if (vAvg >= 0) text += '  \nAvg: ' + f2dp(vAvg * lrgLenMult) + ' ' + lrgLenUnit;
                            if (vSum >= 0) text += '  \nSum: ' + f2dp(vSum * lrgLenMult) + ' ' + lrgLenUnit;
                            break;
                        case 'vlLen':
                            if (vMax >= 0) text += '  \nMax: ' + f3dp(vMax * lrgLenMult) + ' ' + lrgLenUnit;
                            if (vMin >= 0) text += '  \nMin: ' + f3dp(vMin * lrgLenMult) + ' ' + lrgLenUnit;
                            if (vAvg >= 0) text += '  \nAvg: ' + f3dp(vAvg * lrgLenMult) + ' ' + lrgLenUnit;
                            if (vSum >= 0) text += '  \nSum: ' + f3dp(vSum * lrgLenMult) + ' ' + lrgLenUnit;
                            break;
                        case 'sbylLen':
                            if (vMax >= 0) text += '  \nMax: ' + f1dp(vMax * smlLenMult / (lrgLenMult * 1000));
                            if (vMin >= 0) text += '  \nMin: ' + f1dp(vMin * smlLenMult / (lrgLenMult * 1000));
                            if (vAvg >= 0) text += '  \nAvg: ' + f1dp(vAvg * smlLenMult / (lrgLenMult * 1000));
                            if (vSum >= 0) text += '  \nSum: ' + f1dp(vSum * smlLenMult / (lrgLenMult * 1000));
                            break;
                        case 'lTime':
                            if (vMax != null) text += '  \nMax: ' + vMax.toDayHrMinSec();
                            if (vMin != null) text += '  \nMin: ' + vMin.toDayHrMinSec();
                            if (vAvg != null) text += '  \nAvg: ' + vAvg.toDayHrMinSec();
                            if (vSum != null) text += '  \nSum: ' + vSum.toDayHrMinSec();
                            break;
                        case 'sTime':
                            if (vMax != null) text += '  \nMax: ' + vMax.toDayHrMinSec2();
                            if (vMin != null) text += '  \nMin: ' + vMin.toDayHrMinSec2();
                            if (vAvg != null) text += '  \nAvg: ' + vAvg.toDayHrMinSec2();
                            if (vSum != null) text += '  \nSum: ' + vSum.toDayHrMinSec();
                            break;
                        case 'speed':
                            text += '  \nMax: ' + f1dp(vMax * speedMult) + ' ' + speedUnit;
                            text += '  \nMin: ' + f1dp(vMin * speedMult) + ' ' + speedUnit;
                            text += '  \nAvg: ' + f1dp(vAvg * speedMult) + ' ' + speedUnit;
                            text += '  \nSum: ' + f1dp(vSum * speedMult) + ' ' + speedUnit;
                            break;
                    }
                }
                this.title = text;
            })
            .on('click', function(d, i) {
                tableXScroll = $('.fixedHeadTableWrapper table tbody').scrollLeft();

                if (orderCol.field == d.field) {
                    if (orderDir == 'asc') {
                        orderDir = 'desc';
                    } else {
                        orderDir = 'asc';
                    }
                } else {
                    orderDir = d.defOrder;
                }

                orderCol = d;

                setOrder(a, true);

                clearTextSelection();
                dataLoaded();
            })
            .html(function(d) {
                return d.title;
            });

        theadChanginged = false;
    }

    if (offset * rowCount > liveData.length) {
        offset = 0;
    }

    var tr = d3.select('#dataTable tbody')
        .selectAll('tr')
        .data(liveData.slice(offset * rowCount, offset * rowCount + rowCount))
        .enter()
        .append('tr')
        .attr('id', function(d) {
            return 'tr' + d.i;
        })
        .on('mouseover', function(d, i, j) {
            if (mapShownChecked && typeof(d.ll) !== 'undefined') {
                d.ll.setStyle(d.mapLineHighlightStyle ? d.mapLineHighlightStyle : lm_mapLineHighlight);
                d.ll.bringToFront();
                d3.select(this).classed('info', true);
            }
            if (chartShownChecked) {
                d3.selectAll('#chartItem' + d.i)
                    .classed('hover', true);
            }
            if (photosShownChecked) {
              d3.selectAll('.photo' + d.i)
                  .style('background', '#FF032E');
            }
        })
        .on('click', function(d, i, j) {
            if (mapShownChecked && (d3.event.srcElement ? d3.event.srcElement : d3.event.originalTarget).tagName.toUpperCase() != 'A') {
                setExtent([d]);
            }
            if (photosShownChecked && (d3.event.srcElement ? d3.event.srcElement : d3.event.originalTarget).tagName.toUpperCase() != 'A') {
              drawPhotosScroll(d.ai);
            }
        })
        .on('mouseout', function(d, i, j) {
            if (mapShownChecked && typeof(d.ll) !== 'undefined') {
                d.ll.setStyle(d.mapLineStyle ? d.mapLineStyle : lm_mapLine);
                d3.select(this).classed('info', false);
            }
            if (chartShownChecked) {
                d3.selectAll('#chartItem' + d.i)
                    .classed('hover', null);
            }
            if (photosShownChecked) {
              d3.selectAll('.photo' + d.i)
                  .style('background', null);
            }
        });

    var td = tr.selectAll('td')
        .data(columns.filter(function(d, i) {
            return d.displayOrder > -1;
        }))
        .enter()
        .append('td')
        .attr('class', function(d) {
            return d.class
        })
        .style('background-color', function(d) {
            if (d.field == 'drc') {
                var alpha = (14 - Math.min((new Date() - parseDateTime(this.parentNode.__data__['drc'])) / (1000 * 60 * 60 * 24), 14)) / 14;
                if (this.parentNode.__data__['lc'] > 0) {
                    return 'rgba(227,0,0,' + alpha + ')';
                } else {
                    return 'rgba(0,227,0,' + alpha + ')';
                }
            } else {
                return null;
            }
        })
        .classed('ordered', function(d) {
            return ((orderCol == null && d.id == 0) || d == orderCol);
        })
        .attr('title', function(d) {
            if (d.s_s == 1 && !s_s) {
                return 'This data is only visible to Strava Subscribers. Please click to view more information.';
            }
            if (d.field == 'drc') {
                return 'Days since last change: ' + ((new Date() - parseDateTime(this.parentNode.__data__['drc'])) / (1000 * 60 * 60 * 24)).round(0) + '.  Background colour shows last change: Red for down (bad), green for up (good) and the colour saturation shows how long ago (deep red/green today, very faint red/green 2 weeks ago)';
            } else {
                return (typeof(d.maxLen) !== 'undefined' ? this.parentNode.__data__[d.field] : null)
            }
        })
        .classed('ar', function(d) {
            return (d.type == 'num' || d.type == 'geo' ? true : false)
        })
        .classed('visible-desktop', function(d) {
            return d.respDisp == '1'
        })
        .classed('visible-tablet', function(d) {
            return d.respDisp == '2'
        })
        .classed('visible-phone', function(d) {
            return d.respDisp == '3'
        })
        .classed('hidden-desktop', function(d) {
            return d.respDisp == '4'
        })
        .classed('hidden-tablet', function(d) {
            return d.respDisp == '5'
        })
        .classed('hidden-phone', function(d) {
            return d.respDisp == '6'
        })
        .classed('prsDist', function(d) {
            return d.field == 'prsDist' || d.field == 'ksDist'
        })
        .style('display', function(d) {
            return (d.respDisp == '7' ? 'none' : null)
        })
        .html(function(d, i) {
            if (d.s_s == 1 && !s_s) {
                return '<a href="' + s_sBlogLink + '" target="_blank">n/a</a>';
            }
            var v = this.parentNode.__data__[d.field];
            if (typeof(v) === 'undefined' || v == null) {
                v = '';
            }
            if ((d.hideZeros == 1 && v == 0) || v == 'n/a') {
                return '';
            }
            /*if (typeof(d.maxLen) !== 'undefined' && v != null) {
                if (v.length > d.maxLen) {
                    v = v.substr(0, d.maxLen) + '…';
                }
            }*/
            var text = '';
            if (d.class == 'clipText') {
                text += '<div title="' + v.toString().htmlEscape() + '" class=\"outer\"><div class=\"inner\">';
            }
            if (d.class == 'clipText segPRs') {
                text += '<div class=\"outer\"><div class=\"inner\">';
            }
            if (d.field == 'an' || d.field == 'a_an') {
                if (typeof(isMmf) === 'undefined' || !isMmf) {
                    text += '<a title="View activity ' + this.parentNode.__data__[d.field].toString().htmlEscape() + ' on Strava" target="_blank" href="http://www.strava.com/activities/' + this.parentNode.__data__['ai'] + '"><img src="' + https + '://' + assetPrefix + '/img/strava_icon.svg" style="height:16px"></a> <a title="View flyby on Strava" target="_blank" href="http://labs.strava.com/flyby/viewer/#' + this.parentNode.__data__['ai'] + '"><img src="' + https + '://' + assetPrefix + '/img/actViewer.png"></a> <a title="View activity ' + this.parentNode.__data__[d.field].toString().htmlEscape() + ' on VeloViewer" target="_blank" href="/athletes/' + (typeof(contextAthleteId) === 'undefined' ? athleteId : contextAthleteId) + '/activities/' + this.parentNode.__data__['ai'] + '">';
                } else {
                    text += '<a target="_blank" href="http://www.mapmyfitness.com/workout/' + this.parentNode.__data__['ai'] + '"><img src="' + https + '://' + assetPrefix + '/img/mmf.png"></a> <a target="_blank" href="/workouts/' + this.parentNode.__data__['ai'] + '">';
                }
            }
            /*
            activity id
            if (d.field == 'prs') {
              text += '<a target="_blank" href="/athlete/' + contextAthleteId + '/segments?f=';
            }
            */
            if (d.field == 'everestName') {
                text += '<a title="View Everest Infographic" target="_blank" href="http://veloviewer.com/everesting/' + this.parentNode.__data__['ai'] + '"><img src="' + https + '://' + assetPrefix + '/img/everest.png"> ';
            }
            if (d.field == 'routeName') {
                text += '<a title="View route ' + this.parentNode.__data__[d.field].toString().htmlEscape() + ' on Strava" target="_blank" href="http://www.strava.com/routes/' + this.parentNode.__data__['id'] + '"><img src="' + https + '://' + assetPrefix + '/img/strava_icon.svg" style="height:16px"></a>  <a title="View route ' + this.parentNode.__data__[d.field].toString().htmlEscape() + ' on VeloViewer" target="_blank" href="/routes/' + this.parentNode.__data__['id'] + '">';
            }
            if (d.field == 'athleteName') {
                if (typeof(isMmf) === 'undefined' || !isMmf) {
                    text += '<a title="View athlete on Strava" target="_blank" href="http://www.strava.com/athletes/' + this.parentNode.__data__['athleteId'] + '">';
                } else {
                    //text += '<a target="_blank" href="http://www.mapmyfitness.com/workout/' + this.parentNode.__data__['ai'] + '"><img src="'+https+'://' + assetPrefix + '/img/mmf.png"></a> <a target="_blank" href="/workouts/' + this.parentNode.__data__['ai'] + '">';
                }
            }
            if (d.field == 'sn' || d.field == 's_sn') {
                var n = this.parentNode.__data__[d.field] == null ? 'N/A' : this.parentNode.__data__[d.field].toString();
                text += (typeof(isClimbsList) === 'undefined' ? '<a title="View segment ' + n.toString().htmlEscape() + ' on Strava" target="_blank" href="http://www.strava.com/segments/' + this.parentNode.__data__['si'] + '"><img src="' + https + '://' + assetPrefix + '/img/strava_icon.svg" style="height:16px"></a> ' : '') + '<a data-id="' + this.parentNode.__data__['si'] + '" title="View segment ' + n.htmlEscape() + (typeof(isClimbsList) === 'undefined' ? ' on VeloViewer' : ' elevation profile') + '" target="_blank" href="/segments/' + this.parentNode.__data__['si'] + '">' + (typeof(this.parentNode.__data__['k']) !== 'undefined' && this.parentNode.__data__['k'] ? '<img src="https://cf.veloviewer.com/img/kinomap_icon.svg" style="height:16px" title="Segment has Kinomap video, click and view on Kinomap tab."> ' : '') + (n == '' ? '[blank]' : '');
            }
            if (d.field == 'previous') {
                text += '<span class="label';
                if (this.parentNode.__data__['previous'] < 0) {
                    text += ' label-success';
                }
                if (this.parentNode.__data__['previous'] > 0) {
                    text += ' label-warning';
                }
                text += '">';
            }
            switch (d.display) {
                case 'date':
                    text += fDate(parseDateTime(v, (typeof(this.parentNode.__data__['tz']) !== 'undefined' ? this.parentNode.__data__['tz'] : null)));
                    break;
                case 'int':
                    text += v == '' || v == Infinity ? '&nbsp;' : fInt(v);
                    break;
                case '1dp':
                    text += v == Infinity || ((d.field == "_pbl" || d.field == "pbl") && this.parentNode.__data__['bet'] == null) ? '&nbsp;' : f1dp(v);
                    break;
                case '2dp':
                    text += v == Infinity ? '&nbsp;' : f2dp(v);
                    break;
                case '3dp':
                    text += v == Infinity ? '&nbsp;' : f3dp(v);
                    break;
                case 'sLenbyTime':
                case 'sLen':
                    text += fInt(v * smlLenMult);
                    break;
                case 'lLen':
                    text += (v < 0 ? 'n/a' : f2dp(v * lrgLenMult));
                    break;
                case 'vlLen':
                    text += (v < 0 ? 'n/a' : f3dp(v * lrgLenMult));
                    break;
                case 'sbylLen':
                    text += (v < 0 ? 'n/a' : f1dp(v * smlLenMult / (lrgLenMult * 1000)));
                    break;
                case 'lTime':
                    if (v == null || v == '') {
                        text += '&nbsp;'
                    } else {
                        text += (d.field == "_bl" || d.field == "bl") && this.parentNode.__data__['bet'] == null ? '&nbsp;' : parseFloat(v).toDayHrMinSec();
                    }
                    break;
                case 'sTime':
                    if (v == null || v == '') {
                        text += '&nbsp;'
                    } else {
                        text += parseFloat(v).toDayHrMinSec2();
                    }
                    break;
                case 'speed':
                    text += f1dp(v * speedMult);
                    break;
                case 'text':
                    text += v.toString().htmlEscape();
                    break;
                case 'list':
                    text += (v == '1' ? 'Yes' : (v == '0' ? '' : v));
                    break;
                case 'bool':
                    text += (v == '1' ? 'Yes' : '');
                    break;
                case 'segPRs':
                    if (typeof(v) != 'undefined' && v != null) {
                        text += this.parentNode.__data__[d.field + 'Html'];
                    }
                    break;
                case 'prsDist':
                    break;
                case 'arrow':
                    text += f1dp(v) + ' <div class="bearingArrow" style="-moz-transform: rotate(' + v + 'deg);-o-transform: rotate(' + v + 'deg);-webkit-transform: rotate(' + v + 'deg);transform: rotate(' + v + 'deg);"></div>';
            }
            if (d.field == 'activityName' || d.field == 'an' || d.field == 'everestName' || d.field == 'routeName' || d.field == 'sn' || (d.field == 'athleteName' && this.parentNode.__data__['athleteIsPublic'] == '1')) {
                text += '</a>';
            }
            if (d.field == 'if') {
                text = '<a href="#" type="button" class="vvstar" title="Segment is ' + (v == '1' ? '' : 'not ') + 'currently starred. Click to ' + (v == '1' ? 'un' : '') + 'star.">&#' + (v == '1' ? '9733' : '9734') + '</a>';
            }
            if (d.class.indexOf('clipText') > -1) {
                text += '</div></div>';
            }
            if (d.field == 'previous') {
                text += '</span>';
            }
            if (d.title == 'Tries' && text == '0') {
                text = '<span title="In order to see your number of tries you must first visit your summary or activities page in order to get the required data.">?</span>';
            }
            if (!gettingPos && d.type == 'geo') {
                text = '<button title="Press to get your current location and populate this column." style="margin: -5px 5px;padding: 1px 3px;border-width: 0px;" class="initGeo btn btn-mini btn-primary"><i class="icon-screenshot icon-white"></i></button>';
            }
            return text;
        });

    d3.selectAll('.vvstar')
        .on('click', function() {
            d3.event.preventDefault();
            var d = this.parentElement.parentElement.__data__;
            $.ajax({
                url: '/api/setSegmentStarred.php?i=' + d.si + '&v=' + (d.if ? 0 : 1),
                obj: this,
                val: (d.if ? 0 : 1),
                si: d.si,
                async: true,
                success: function(result) {
                    if (typeof(result.success) !== 'undefined' && result.success) {
                        this.obj.parentElement.parentElement.__data__.if = this.val;
                        if (this.val == 1) {
                            starred.push(this.si);
                        } else {
                            var i = starred.indexOf(this.si);
                            if (i > -1) {
                                starred.splice(i, 1);
                            }
                        }
                        a.filter(function(d) {
                            return d.si == this.si
                        }, this).forEach(function(d) {
                            d.if = this.val;
                        }, this);
                        dataLoaded();
                    } else {
                        alert('Failed to ' + (this.val == 1 ? '' : 'un') + 'star segment :-(');
                    }
                }
            });
            return false;
        });

    d3.selectAll('.initGeo').on('click', function() {
        if (navigator.geolocation) {
            gettingPos = true;
            navigator.geolocation.getCurrentPosition(
                function(position) {
                    curLat = position.coords.latitude;
                    curLng = position.coords.longitude;
                    initGeo();
                },
                function(error) {
                    alert('Your current location isn\'t available.  Make sure you have enabled your location services in your device and browser for VeloViewer.com and if you are using Safari that you are connected to a wireless network. If you are using Chrome then you must use https rather than http in the URL.');
                });
        } else {
            alert('Your current location isn\'t available.  Make sure you have enabled your location services in your device and browser for VeloViewer.com  If you are using Chrome then you must use https rather than http in the URL.');
        }
    })

    d3.selectAll('#dataTable tbody td.prsDist').each(function(d) {
        var td = d3.select(this).style('padding', '1px');
        if (d.s_s == 1 && !s_s) {
            td.html('<a href=-"' + s_sBlogLink + '" target="_blank">n/a</a>');
            return;
        }

        var tdWidth = parseInt(td.style('width'));
        td.html('');
        if (this.parentNode.__data__[d.field].length > 0) {
            var y = d3.scale.linear()
                .range([18, 0])
                .domain([0, d3.max(this.parentNode.__data__[d.field], function(e) {
                    return e.l
                })]);
            td.append('svg')
                .style('height', '18px')
                .style('width', (isNaN(tdWidth) ? 60 : tdWidth) + 'px')
                .selectAll('rect')
                .data(this.parentNode.__data__[d.field])
                .enter()
                .append('rect')
                .style('fill', '#FF032E')
                .style('strokeWidth', '0px')
                .attr('width', ((isNaN(tdWidth) ? 60 : tdWidth) / 10) + 'px')
                .attr('x', function(e) {
                    return (e.i * (isNaN(tdWidth) ? 60 : tdWidth) / 100) + 'px'
                })
                .attr('y', function(e) {
                    return y(e.l) + 'px'
                })
                .attr('height', function(e) {
                    return (18 - y(e.l)) + 'px'
                })
                .append('title')
                .text(function(e) {
                    return e.l + ' segments with ' + (this.parentNode.parentNode.parentNode.__data__.field == 'prsDist' ? 'PR' : 'KOM') + ' scores between ' + e.i + ' and ' + (e.i + 10)
                });
        }
    })

    if (!noMapZoom && (typeof(map) != 'undefined') && mapShownChecked) {
        //var isOS = map.baseLayer.name.indexOf('Survey') > -1;

        a.filter(function(d) {
            if (!liveDataObj[d.i] && d.shownOnMap) {
                map.removeLayer(d.ll);
                d.shownOnMap = false;
            }
        })

        liveData.reverse();
        liveData.forEach(function(d, i) {
            if (d.map) {
                if (typeof(d.ll) === 'undefined') {
                    var linePoints = [];
                    var l;

                    if (typeof(d.ll1) !== 'undefined') {
                        d.ll = d.ll1;
                        delete d.ll1;
                    } else {
                        d.ll = decompressN(d.map, 5, 2);
                        d.ll = d.ll.slice(0, d.ll.length / 2).map(function(e, i) {
                            return [d.ll[i * 2], d.ll[i * 2 + 1]]
                        });
                    }
                    var latExt = d3.extent(d.ll, function(e) { return e[0] });
                    var lngExt = d3.extent(d.ll, function(e) { return e[1] });
                    d.minLat = latExt[0];
                    d.maxLat = latExt[1];
                    d.minLng = lngExt[0];
                    d.maxLng = lngExt[1];
                    d.ll = new L.Polyline(d.ll, d.mapLineStyle ? d.mapLineStyle : lm_mapLine);
                    d.ll.isPopupOpen = false;
                    d.ll.vv_type = 'mapLine';
                    d.ll.vv_id = d.i;
                    d.ll.bindPopup('<a title="View on VeloViewer" href="/' + (cpref == 's' ? 'segments' : (cpref == 'r' ? 'routes' : 'activities')) + '/' + d.i + '" target="_blank">' + d[cpref == 'r' ? 'routeName' : cpref + 'n'] + '</a> <a title="View on Strava" target="_blank" href="https://www.strava.com/' + (cpref == 's' ? 'segments' : (cpref == 'r' ? 'routes' : 'activities')) + '/' + d.i + '"><img width="16" height="16" src="' + https + '://' + assetPrefix + '/img/strava_icon.svg" style="width:16px;height:16px"></a>' + (cpref == 's' ? ' ' + suf(d.r) + '&nbsp;of&nbsp;' + d.tac : ''));
                    d.ll.on('mouseover', function(e) {
                        d3.select('#tr' + e.target.vv_id).classed('info', true);
                        e.target.setStyle(d.mapLineHighlightStyle ? d.mapLineHighlightStyle : lm_mapLineHighlight);
                        e.target.bringToFront();
                    });
                    d.ll.on('mouseout', function(e) {
                        if (!d.ll.isPopupOpen) {
                            d3.select('#tr' + e.target.vv_id).classed('info', false);
                            e.target.setStyle(d.mapLineStyle ? d.mapLineStyle : lm_mapLine);
                        }
                    });
                    map.addLayer(d.ll);
                } else {
                    if (!d.shownOnMap) {
                        map.addLayer(d.ll);
                    }
                }
                d.shownOnMap = true;
            }
        });

        liveData.reverse();

        if (photoLayer) {
          photoLayer.clear();

          photoData = d3.merge(liveData.filter(function(d) {
            return d.photos && d.photos.filter(function(e) {
              return e.l
            }).length > 0
          }).map(function(d) {
            return d.photos.filter(function(e) {
              return e.l
            });
          }));

          photoLayer.add(photoData);
          if (+getCookie('PhotosShown') || typeof(getCookie('PhotosShown')) === 'undefined') {
            photoLayer.addTo(map);
          }
        }

        if (!map.getZoom()) {
            var bounds = [
                [100000, 100000],
                [-100000, -100000]
            ];
            var justVirtual = true;
            if (liveData.length > 0 && liveData[0].t) {
                for (var i = 0; i < liveData.length; i++) {
                    if (liveData[i].t.indexOf('Virtual') == -1) {
                        justVirtual = false;
                        break;
                    }
                }
            }
            liveData.filter(function(d) { return !d.t || ((justVirtual || d.t.indexOf('Virtual') == -1) && d.minLat) }).forEach(function(d) {
                bounds[0][0] = Math.min(bounds[0][0], d.minLat);
                bounds[1][0] = Math.max(bounds[1][0], d.maxLat);
                bounds[0][1] = Math.min(bounds[0][1], d.minLng);
                bounds[1][1] = Math.max(bounds[1][1], d.maxLng);
            });
            map.fitBounds(bounds);
        } else {
            var mapFilterId = jsonIndexOf(columns, 'field', 'mapFilter');

            if (document.getElementById('mapAutoZoomCheckBox') != null && document.getElementById('mapAutoZoomCheckBox').checked &&
                (mapFilterId == -1 || columns[mapFilterId].filterActive == 0)) {
                setExtent(liveData);
            }
        }
    }

    if (offset == 0) {
      if (chartShownChecked) {
          drawChart();
      }
      if (photosShownChecked) {
        drawPhotos();
      }
    }

    d3.select('#prevBatchContainer')
        .classed('disabled', (offset == 0 ? true : false));

    d3.select('#nextBatchContainer')
        .classed('disabled', ((offset + 1) * rowCount >= liveData.length ? true : false));

    d3.select('#actualRowCount')
        .text((offset * rowCount + 1) + '-' + (Math.min((offset + 1) * rowCount, liveData.length)) + ' of ' + liveData.length);

    updateTime = new Date() - startTime;
    setTimeout(function() {
        d3.select('#loadingMessage').style('display', 'none');
    }, 1);

    if (embed) {
        top.postMessage(d3.select('body').style('height'), 'http://www.' + embedType + '.cc');
    }

    if (typeof(filtersFinishedFun) !== 'undefined') {
        if (filtersFinishedTimer != null) {
            clearTimeout(filtersFinishedTimer);
        }
        filtersFinishedTimer = window.setTimeout(filtersFinishedFun, 200);
    }
    if (!noWriteUrl) writeURL();
}

var margin = {
        top: 20,
        right: 10,
        bottom: 0,
        left: 10
    },
    width = 250 - margin.left - margin.right,
    height = 75 - margin.top - margin.bottom;
var filterCols = 100;

var minET, maxET, minElapsedTime, maxElapsedTime, timeStep, xAxis, updateTime;

var minLat = 10000000000;
var maxLat = -10000000000;
var minLng = 10000000000;
var maxLng = -10000000000;

function initGeo() {
    columns.forEach(function(d, i) {
        if (d.type == 'geo') {
            for (j = 0; j < a.length; j++) {
                if (typeof(a[j].map) === 'undefined' || a[j].map == '') {
                    a[j][d.field] = 0;
                } else {
                    llArr = decompressN(a[j].map, 5, 2);
                    if (((curLat == 0) && (curLng == 0)) ||
                        ((llArr[0] == 0) && (llArr[1] == 0))) {
                        a[j][d.field] = 0
                    } else {
                        a[j][d.field] = distLatLon(curLat, curLng, llArr[0], llArr[1]);
                    }
                }
            }
        }
    });
    if (orderCol.field == 'startLngLat') {
        setOrder(a, true);
    }
    dataLoaded();
}

function initOther() {
    // this might need to be extended for things that don't have an et
    a = a.filter(function(d) {
        return typeof(d.distIntoRoute) !== 'undefined' || typeof(d.et) !== 'undefined' || typeof(d.segmentId) !== 'undefined' || typeof(d.elapsedTime) !== 'undefined' || cpref == 'r'
    });
    columns.forEach(function(d, i) {
        if (typeof(d.class) === 'undefined') {
            d.class = '';
        }
        if (typeof(d.lngDesc) === 'undefined') {
            d.lngDesc = d.desc;
        }
        if (d.type == 'geo') {
            for (j = 0; j < a.length; j++) {
                if (((curLat == 0) && (curLng == 0)) ||
                    ((a[j][d.lat] == 0) && (a[j][d.lng] == 0))) {
                    a[j][d.field] = -1
                } else {
                    a[j][d.field] = distLatLon(curLat, curLng, a[j][d.lat], a[j][d.lng]);
                }
            }
        }
        if (d.type == 'smlPace') {
            for (j = 0; j < a.length; j++) {
                //a[j][d.field] = a[j]['elapsedTime'] / (a[j]['distance'] / smlPaceMult);
                a[j][d.field] = parseFloat(a[j][d.field.replace('Sml', '')]) * smlPaceMult;
                a[j][d.field] = (isNaN(a[j][d.field]) ? 0 : a[j][d.field]);
            }
        }
        if (d.type == 'lrgPace') {
            for (j = 0; j < a.length; j++) {
                //a[j][d.field] = a[j]['elapsedTime'] / (a[j]['distance'] / lrgPaceMult);
                a[j][d.field] = parseFloat(a[j][d.field.replace('Lrg', '')]) * lrgPaceMult;
                a[j][d.field] = (isNaN(a[j][d.field]) ? 0 : a[j][d.field]);
            }
        }
        if (d.type == 'date') {
            for (j = 0; j < a.length; j++) {
                a[j][d.field + 'Ms'] = (parseDateTime(a[j][d.field]) == null ? null : parseDateTime(a[j][d.field]).getTime());
            }
        }
        if (d.type == 'segPRs') {
            if (!isBeta) {
                if (segArr == null) {
                    if (hasLocalStorage && typeof(localStorage['segments' + contextAthleteId]) != 'undefined') {
                        segArr = [];
                        var temp = jQuery.parseJSON(localStorage['segments' + contextAthleteId]);
                        temp.forEach(function(e) {
                            var d = jQuery.parseJSON(LZString.decompress(e));
                            segArr[d.i] = d;
                        });
                    }
                }
                for (j = 0; j < a.length; j++) {
                    if (a[j][d.field] == null) {
                        a[j][d.field + 'Ord1'] = 'A';
                        a[j][d.field + 'Ord2'] = 0;
                    } else {
                        var segHtml = '';
                        /*try
          {*/
                        var ta = [];
                        var segs = [];
                        if (a[j][d.field] != '') {
                            a[j][d.field].split(',').forEach(function(e) {
                                var da = e.split('|');
                                if (d.field == 'sp') {
                                    if (segArr != null && segArr[da[0]] && segArr[da[0]].ai == a[j].ai) {
                                        da[1] = segArr[da[0]].r;
                                        da[2] = segArr[da[0]].tac;
                                    }
                                }

                                if (typeof(da[1]) !== 'undefined' && da[1] != -1) {
                                    segs.push({
                                        'id': da[0],
                                        'rank': da[1],
                                        'total': da[2]
                                    });

                                    if (jsonIndexOf(ta, 'pos', parseInt(da[1])) == -1) {
                                        ta.push({
                                            'pos': parseInt(da[1]),
                                            'count': 0,
                                            'sumAth': 0
                                        });
                                    }
                                    var ind = jsonIndexOf(ta, 'pos', parseInt(da[1]));
                                    ta[ind].count++;
                                    ta[ind].sumAth += parseInt(da[2]);
                                }
                            });
                            var strVal = '';
                            var curPos = 1;
                            ta.sort(function(a, b) {
                                return a.pos - b.pos
                            }).forEach(function(d) {
                                while (curPos < d.pos) {
                                    strVal += 'A';
                                    curPos++;
                                }
                                strVal += String.fromCharCode(65 + d.count);
                                curPos++;
                            });
                            a[j][d.field + 'Ord1'] = strVal;
                            a[j][d.field + 'Ord2'] = (ta.length > 0 ? ta[0].sumAth : 0);

                            segs.sort(function(a, b) {
                                    return (a.rank == b.rank ? b.total - a.total : a.rank - b.rank)
                                })
                                .filter(function(d) {
                                    return (segArr == null || segArr[d.id])
                                })
                                .forEach(function(s) {
                                    var st = (segArr == null && d.field == 'sp' ? '?' : suf(s.rank)) + ' of ' + (segArr == null && d.field == 'sp' ? '?' : s.total);
                                    if (segArr != null) {
                                        if (segArr[s.id]) {
                                            st += ' - ' + segArr[s.id].sn;
                                        }
                                    } else {
                                        if (d.field == 'sp') {
                                            st += ' - to see segment names and placings on your PRs then navigate segment list then back to this page in order to get your segment data.';
                                        } else {
                                            st += ' - to see segment names then navigate segment list then back to this page in order to get your segment data.';
                                        }
                                    }
                                    segHtml += '<a title="' + st + '" target="_blank" href="/segments/' + s.id + '"><span class="label" style="background-color:hsla(' + hueScale(Math.min(s.rank, 100)) + ',100%,35%,' + opacityScale(Math.min(s.total, 50)) + ')">' + (segArr == null && d.field == 'sp' ? '?' : s.rank) + '</span></a> ';
                                });
                        }
                        /*}
          catch (err)
          {}*/
                        a[j][d.field + 'Html'] = segHtml;
                    }
                }
            }
        }
    });
}

function setExtent(pData) {
    var ld = pData.filter(function(d) {
        return typeof(d.minLat) !== 'undefined'
    });
    if (ld.length > 0) {
        minLat = 10000000000;
        maxLat = -10000000000;
        minLng = 10000000000;
        maxLng = -10000000000;

        ld.forEach(function(d, i) {
            if (d.minLat < minLat) {
                minLat = d.minLat;
            }
            if (d.maxLat > maxLat) {
                maxLat = d.maxLat;
            }
            if (d.minLng < minLng) {
                minLng = d.minLng;
            }
            if (d.maxLng > maxLng) {
                maxLng = d.maxLng;
            }
        });

        /*var isOS = map.baseLayer.name.indexOf('Survey') > -1;

        if (isOS) {
            minLng = Math.max(minLng, -3367);
            maxLng = Math.min(maxLng, 705382);
            minLat = Math.max(minLat, -5079);
            maxLat = Math.min(maxLat, 1302920);
        }*/
        if (typeof(map) !== 'undefined') {
            //extent = new OpenLayers.Bounds(minLng, minLat, maxLng, maxLat).transform((isOS ? toProjection : fromProjection), toProjection);
            //map.zoomToExtent(extent);
            map.fitBounds([
                [minLat, minLng],
                [maxLat, maxLng]
            ]);
        }
    }
}

function setMapFilterText(d) {
    d3.select('#mapFilterText')
        .html(function(d) {
            return 'Top left: ' + f5dp(d.maxLat) + ', ' + f5dp(d.minLng) + '<br/>Bottom right: ' + f5dp(d.minLat) + ', ' + f5dp(d.maxLng);
        });
}

function initFilter(col) {
    col.data = new Array(100);

    switch (col.type) {
        case 'num':
        case 'geo':
        case 'lrgPace':
        case 'smlPace':
            //col.min = Math.min.apply(Math,a.map(function(o){return (col.hideZeros == 0 ? parseFloat(o[col.field]) : 10000000);}));
            col.min = col.display == 'arrow' ? -90 : Math.min.apply(Math, a.map(function(o) {
                var v = parseFloat(o[col.field] == null || isNaN(o[col.field]) ? '0' : o[col.field]);
                return (v == 0 && col.hideZeros == 1 ? '10000000' : v);
            }));
            col.max = col.display == 'arrow' ? 360 + 90 : Math.max.apply(Math, a.map(function(o) {
                return parseFloat(o[col.field] == null || isNaN(o[col.field]) ? '0' : o[col.field]);
            }));
            break;
        case 'date':
            col.min = Math.min.apply(Math, a.map(function(o) {
                return parseDateTime(o[col.field]).getTime();
            }));
            col.max = Math.max.apply(Math, a.map(function(o) {
                return parseDateTime(o[col.field]).getTime();
            }));
            break;
        case 'list':
            col.keys = d3.keys(d3.nest()
                .key(function(d) {
                    return d[col.field];
                })
                .map(a))
            return;
    }

    /*if (col.min == col.max && col.max == 0)
    {
      col.max = 1;
    }
    else
    {
      col.min = Math.min(col.min, col.max - 1);
    }*/

    col.timeStep = (col.max - col.min) / filterCols;

    //col.min--;
    //col.filterMin = col.display == 'arrow' ? 0 : col.min;
    //col.filterMax = col.display == 'arrow' ? 360 : col.max;
    col.filterMin = col.min;
    col.filterMax = col.max;

    for (j = 0; j <= filterCols; j++) {
        col.data[j] = {
            id: j,
            min: j * col.timeStep,
            max: (j + 1) * col.timeStep,
            count: 0
        };
    }

    for (j = 0; j < a.length; j++) {
        switch (col.type) {
            case 'num':
            case 'geo':
            case 'lrgPace':
            case 'smlPace':
                a[j][col.field + 'Col'] = Math.floor((a[j][col.field] - col.filterMin) * 100 / Math.max(0.000001, (col.filterMax - col.filterMin)));
                break;
            case 'date':
                a[j][col.field + 'Col'] = Math.floor((parseDateTime(a[j][col.field]).getTime() - col.filterMin) * 100 / Math.max(0.000001, (col.filterMax - col.filterMin)));
                break;
        }

        if (a[j][col.field + 'Col'] >= 0) {
            col.data[a[j][col.field + 'Col']].count++;
        }
    }

    col.x = d3.scale.linear().range([0, width - 2]);
    col.x1 = d3.scale.linear().range([1, width]);
    col.xb = d3.scale.linear().range([0, width + 1]);
    if (col.type == 'geo') {
        col.y = d3.scale.sqrt().range([height - 2, 0]);
    } else {
        col.y = d3.scale.linear().range([height - 2, 0]);
    }

    col.x.domain(d3.extent(col.data.map(function(d) {
        return d.id;
    })));
    col.xb.domain(d3.extent(col.data.map(function(d) {
        return d.id;
    })));
    col.y.domain([0, d3.max(col.data.map(function(d) {
        return d.count;
    }))]);

    col.area = d3.svg.area()
        .interpolate("step-after");
    /*  .x(function(d) {
      return this.__data__.x(d.id) - 1; })
    .y0(function(d){
      return height+25;
    })
    .y1(function(d) {
      return this.__data__.col.y(d.count); });*/

    if (typeof(xAxis) === 'undefined') {
        xAxis = d3.svg.axis().scale(col.x1).orient("bottom");
    }
}

function setFilterMinMax(g, pd) {
    var text = '';
    var minMax = d3.select(g.parentNode.parentNode).select('.filterMinMax');

    minMax.html('');

    minMax.append('input')
        .attr('id', function(d) {
            return 'min' + d.id;
        })
        .attr('type', 'text')
        .classed('dateFilter', function(d) {
            return d.display == 'date' || d.display == 'lTime'
        })
        .attr('value', function(d) {
            switch (d.display) {
                case 'int':
                    return fInt(d.filterMin);
                case '1dp':
                case 'arrow':
                    return f1dp(d.filterMin);
                case '2dp':
                    return f2dp(d.filterMin);
                case '3dp':
                    return f3dp(d.filterMin);
                case 'sLen':
                    return fInt(d.filterMin * smlLenMult);
                case 'lLen':
                    return f2dp(d.filterMin * lrgLenMult);
                case 'vlLen':
                    return f3dp(d.filterMin * lrgLenMult);
                case 'speed':
                    return f1dp(d.filterMin * speedMult);
                case 'sbylLen':
                    return f1dp(d.filterMin * smlLenMult / (lrgLenMult * 1000));
                case 'sLenbyTime':
                    return fInt(d.filterMin * smlLenMult);
                case 'date':
                    return fDate(parseDateTime(d.filterMin));
                case 'sTime':
                case 'lTime':
                    return d.filterMin.toDayHrMinSec2()
            }
        })
        .on('change', function(d) {
            var val = this.value;
            switch (d.display) {
                case 'sLen':
                    val = val / smlLenMult;
                    break;
                case 'lLen':
                case 'vlLen':
                    val = val / lrgLenMult;
                    break;
                case 'speed':
                    val = val / speedMult;
                    break;
                case 'sbylLen':
                    val = (lrgLenMult * 1000) * val / smlLenMult;
                    break;
                case 'sLenbyTime':
                    val = val / smlLenMult;
                    break;
                case 'date':
                    var tDate = fDate.parse(val);
                    if (tDate == null) {
                        tDate = fDate2.parse(val);
                    }
                    val = tDate.getTime();
                    break;
                case 'sTime':
                    //val = sTime.parse(val).setYear(1970) / 1000;
                    //break;
                case 'lTime':
                    var tDate = fTime.parse(val.replace(/\./g, ':'));
                    if (tDate == null) {
                        tDate = fTime.parse('00:' + val.replace(/\./g, ':'));
                        if (tDate == null) {
                            tDate = fTime.parse('00:00:' + val.replace(/\./g, ':'));
                        }
                    }
                    val = tDate.setYear(1970) / 1000 - (tDate.getTimezoneOffset() * 60);
                    break;
            }
            d.filterMin = Math.max(Math.min(parseFloat(val), d.filterMax), d.min);
            dataLoaded();
            updateActiveFilters();
            document.getElementById('min' + d.id).focus();
        });
    minMax.append('span')
        .text(function(d) {
            switch (d.display) {
                case 'sLen':
                    return smlLenUnit + ' to ';
                case 'lLen':
                case 'vlLen':
                    return lrgLenUnit + ' to ';
                case 'speed':
                    return speedUnit + ' to ';
                case 'sbylLen':
                    return smlLenUnit + '/' + lrgLenUnit + ' to ';
                case 'sLenbyTime':
                    return smlLenUnit + '/h to ';
            }
            return ' to ';
        });
    minMax.append('input')
        .attr('id', function(d) {
            return 'max' + d.id;
        })
        .attr('type', 'text')
        .classed('dateFilter', function(d) {
            return d.display == 'date' || d.display == 'lTime'
        })
        .attr('value', function(d) {
            switch (d.display) {
                case 'int':
                    return fInt(d.filterMax);
                case '1dp':
                case 'arrow':
                    return f1dp(d.filterMax);
                case '2dp':
                    return f2dp(d.filterMax);
                case '3dp':
                    return f3dp(d.filterMax);
                case 'sLen':
                    return fInt(d.filterMax * smlLenMult);
                case 'lLen':
                    return f2dp(d.filterMax * lrgLenMult);
                case 'vlLen':
                    return f3dp(d.filterMax * lrgLenMult);
                case 'speed':
                    return f1dp(d.filterMax * speedMult);
                case 'sbylLen':
                    return f1dp(d.filterMax * smlLenMult / (lrgLenMult * 1000));
                case 'sLenbyTime':
                    return fInt(d.filterMax * smlLenMult);
                case 'date':
                    return fDate(parseDateTime(d.filterMax))
                case 'sTime':
                case 'lTime':
                    return d.filterMax.toDayHrMinSec2()
            }
        })
        .on('change', function(d) {
            var val = this.value;
            switch (d.display) {
                case 'sLen':
                    val = val / smlLenMult;
                    break;
                case 'lLen':
                case 'vlLen':
                    val = val / lrgLenMult;
                    break;
                case 'speed':
                    val = val / speedMult;
                    break;
                case 'sbylLen':
                    val = (lrgLenMult * 1000) * val / smlLenMult;
                    break;
                case 'sLenbyTime':
                    val = val / smlLenMult;
                    break;
                case 'date':
                    var tDate = fDate.parse(val);
                    tDate.setHours(24);
                    tDate.setSeconds(-1);
                    if (tDate == null) {
                        tDate = fDate2.parse(val);
                    }
                    val = tDate.getTime();
                    break;
                case 'sTime':
                    //val = sTime.parse(val).setYear(1970) / 1000;
                    //break;
                case 'lTime':
                    var tDate = fTime.parse(val.replace(/\./g, ':'));
                    if (tDate == null) {
                        tDate = fTime.parse('00:' + val.replace(/\./g, ':'));
                        if (tDate == null) {
                            tDate = fTime.parse('00:00:' + val.replace(/\./g, ':'));
                        }
                    }
                    val = tDate.setYear(1970) / 1000 - (tDate.getTimezoneOffset() * 60);
                    break;
            }
            d.filterMax = Math.min(Math.max(parseFloat(val), d.filterMin), d.max);
            dataLoaded();
            updateActiveFilters();
            document.getElementById('max' + d.id).focus();
        });
    minMax.append('span')
        .text(function(d) {
            switch (d.display) {
                case 'sLen':
                    return smlLenUnit;
                case 'lLen':
                case 'vlLen':
                    return lrgLenUnit;
                case 'speed':
                    return speedUnit;
                case 'sbylLen':
                    return smlLenUnit + '/' + lrgLenUnit;
                case 'sLenbyTime':
                    return smlLenUnit + '/h';
            }
            return '';
        });
}

var refreshTimer;

function filterFilters() {
    d3.selectAll('#filtersContainer .filteredPath path').remove();

    d3.selectAll('#filtersContainer .filteredPath')
        .append("path")
        .classed('filtered', true)
        .datum(function(d) {
            return d.filteredData;
        })
        .attr("clip-path", "url(#clip)")
        .attr("d",
            d3.svg.area()
            .interpolate("step-after")
            .x(function(d) {
                return this.parentNode.__data__.x(d.id);
            })
            .y0(height)
            .y1(function(d) {
                return this.parentNode.__data__.y(d.count);
            })
        );
}

var textTimer;

function updateText(input, col) {
    if (typeof(col.filterVal) === 'undefined') {
        col.filterVal = '';
    }

    if (col.filterVal != input.value.toLowerCase()) {
        col.filterVal = input.value.toLowerCase();
        clearTimeout(textTimer);
        textTimer = setTimeout(function() {
            dataLoaded()
        }, Math.min(updateTime * 1.1, 250));
    }
}

//var filterCounter = 0;

function updateActiveFilters() {
    var div = d3.select('#filtersContainer').html('');

    var div = d3.select('#filtersContainer')
        .selectAll('div.filterDivList')
        .data(filterActiveColumns.filter(function(d, i) {
            return (d.type == 'list');
        }), function(d) {
            return d.field;
        });

    // set existing checkboxes
    div.forEach(function(d, i) {
        if (typeof(d[0]) !== 'undefined' && d[0] != null) {
            if (typeof(d[0].__data__.filterSelectedItems) === 'undefined') {
                d[0].__data__.filterSelectedItems = [];

                $(d[0]).find('input').each(function(i) {
                    this.checked = true;
                    this.parentElement.parentElement.__data__.filterSelectedItems.push(this.value);
                });
            } else {
                $(d[0]).find('input').each(function(i) {
                    this.checked = this.parentElement.parentElement.__data__.filterSelectedItems.indexOf(this.parentElement.__data__) > -1;
                });
            }
        }
    });


    var newDiv = div.enter()
        .append('div')
        .classed('filterDivList', true);

    newDiv.append('p')
        .classed('filterTitle', true)
        .text(function(d) {
            return d.desc;
        });

    var label = newDiv.append('div')
        .classed('listFilterContainer', true)
        .selectAll('label')
        .data(function(d) {
            return d.keys.sort(function(a, b) {
                a = a == 'Monday' ? 0 : a;
                b = b == 'Monday' ? 0 : b;
                a = a == 'Tuesday' ? 1 : a;
                b = b == 'Tuesday' ? 1 : b;
                a = a == 'Wednesday' ? 2 : a;
                b = b == 'Wednesday' ? 2 : b;
                a = a == 'Thursday' ? 3 : a;
                b = b == 'Thursday' ? 3 : b;
                a = a == 'Friday' ? 4 : a;
                b = b == 'Friday' ? 4 : b;
                a = a == 'Saturday' ? 5 : a;
                b = b == 'Saturday' ? 5 : b;
                a = a == 'Sunday' ? 6 : a;
                b = b == 'Sunday' ? 6 : b;
                return a < b ? -1 : 1
            });
        })
        .enter()
        .append('label')
        .attr('class', 'checkbox inline');

    label.append('input')
        .attr('type', 'checkbox')
        .attr('checked', function(d) {
            var lCol = d3.select(this.parentNode.parentNode).data()[0];

            if (typeof(lCol.filterSelectedItems) === 'undefined') {
                lCol.filterSelectedItems = [];

                d3.select(this.parentNode.parentNode).data()[0].keys.forEach(function(d, i) {
                    lCol.filterSelectedItems.push(d);
                });
                return 'checked';
            } else {
                if (lCol.filterSelectedItems.indexOf(d) > -1) {
                    return 'checked';
                } else {
                    return null;
                }
            }
        })
        .attr('value', function(d) {
            return d;
        })
        .on('click', function(d) {
			if (d3.event.altKey) {
				var c = this.checked;
				d3.select(this.parentNode.parentNode).selectAll('input')[0].forEach(function(d) {
	                d.checked = c;
	            });
				this.checked = !c;
			}
            var lCol = d3.select(this.parentNode.parentNode).data()[0];
            lCol.filterSelectedItems = [];

            d3.select(this.parentNode.parentNode).selectAll('input')[0].forEach(function(d, i) {
                if (d.checked) {
                    lCol.filterSelectedItems.push(d.__data__);
                }
            });

            offset = 0;
            dataLoaded();
        });

    label.append('text')
        .html(function(d) {
            return (d == 0 ? 'No' : (d == 1 ? 'Yes' : d));
        });

    newDiv.append('button')
        .classed('closeBtn', true)
        .on('click', function(d) {
            offset = 0;
            d.filterActive = 0;
            setFilterActive();
            updateActiveFilters();
            updateAvailableFilters();
            dataLoaded();
        })
        .text('X');

    div.exit()
        .remove();


    div = d3.select('#filtersContainer')
        .selectAll('div.filterDivText')
        .data(filterActiveColumns.filter(function(d, i) {
            return (d.type == 'text');
        }), function(d) {
            return d.field;
        });

    var newDiv = div.enter()
        .append('div')
        .classed('filterDivText', true);

    newDiv.append('p')
        .classed('filterTitle', true)
        .text(function(d) {
            return d.desc;
        });

    var input = newDiv.append('input')
        .attr('type', 'text')
        /*.attr('data-provide', 'typeahead')
        .attr('source', 'typeahead')*/
        .attr('value', function(d) {
            var lCol = d3.select(this.parentNode).data()[0];
            return lCol.filterVal
        })
        .on('change', function(d) {
            updateText(this, d);
        })
        .on('keypress', function(d) {
            updateText(this, d);
        })
        .on('paste', function(d) {
            updateText(this, d);
        })
        .on('input', function(d) {
            updateText(this, d);
        });

    newDiv.append('button')
        .classed('closeBtn', true)
        .on('click', function(d) {
            offset = 0;
            d.filterActive = 0;
            setFilterActive();
            updateActiveFilters();
            updateAvailableFilters();
            dataLoaded();
        })
        .text('X');

    div.exit()
        .remove();


    div = d3.select('#filtersContainer')
        .selectAll('div.filterDiv')
        .data(filterActiveColumns.filter(function(d, i) {
            return (d.type == 'date' || d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'num' || d.type == 'geo');
        }), function(d) {
            return d.field;
        });

    //UPDATE
    //div.select('.filterUpdated')
    //.text(function(d) { filterCounter++; return d.field + filterCounter; });

    //NEW
    var newDiv = div.enter()
        .append('div')
        .classed('filterDiv', true);

    var newSvg = newDiv.append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom);

    newSvg.append('text')
        .classed('filterTitle', true)
        .attr('x', 10)
        .attr('y', 12)
        .text(function(d) {
            return d.desc;
        });

    var filterObj = newSvg.append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    filterObj.append("path")
        .classed('all', true)
        .datum(function(d) {
            return d.data;
        })
        .attr("clip-path", "url(#clip)")
        .attr("d",
            d3.svg.area()
            .interpolate("step-after")
            .x(function(d) {
                return this.parentNode.__data__.x(d.id);
            })
            .y0(height)
            .y1(function(d) {
                return this.parentNode.__data__.y(d.count);
            })
        );

    filterObj.append('g')
        .classed('filteredPath', true);

    if (typeof(xAxis) !== 'undefined') {
        filterObj.append("g")
            .attr("class", "x faxis")
            .attr("transform", "translate(0," + (height - 1) + ")")
            .call(xAxis);
    }

    newDiv.append('div')
        .classed('filterMinMax', true);
    /*.attr('x', -20)
      .attr('y', 12)
      .attr("text-anchor", "end")
      .attr("transform", "translate(" +(width+margin.right) + ",0)");*/

    newDiv.append('button')
        .classed('closeBtn', true)
        .on('click', function(d) {
            d.filterMin = d.min;
            d.filterMax = d.max;

            d.filterActive = 0;
            setFilterActive();
            updateActiveFilters();
            updateAvailableFilters();
            dataLoaded();
            setMapChartHeight();
        })
        .text('X');

    newDiv.selectAll('svg > g')
        .forEach(function(d, i) {
            brushes.push(d3.svg.brush()
                .x(filterActiveColumns.filter(function(d, i) {
                    return (d.type == 'date' || d.type == 'lrgPace' || d.type == 'smlPace' || d.type == 'num' || d.type == 'geo');
                })[i].xb)
                .on("brush", function(a) {
                    var b = brushes[a.brushId];
                    if (b.empty()) {
                        this.__data__.filterMin = this.__data__.min;
                        this.__data__.filterMax = this.__data__.max;
                    } else {
                        this.__data__.filterMin = b.extent()[0] * this.__data__.timeStep + this.__data__.min;
                        this.__data__.filterMax = (b.extent()[1] == 100 ? this.__data__.max : b.extent()[1] * this.__data__.timeStep + this.__data__.min);
                    }
                    offset = 0;

                    clearTimeout(refreshTimer);

                    setFilterMinMax(this.parentNode, this.__data__);

                    refreshTimer = setTimeout(function() {
                        dataLoaded()
                    }, Math.min(updateTime * 1.1, 250));
                    setBrushDash();
                }));

            d.parentNode.__data__.brushId = brushes.length - 1;

            if (typeof(d.parentNode.__data__.filterMin) !== 'undefined' && d.parentNode.__data__.filterMin != null) {
                if (!(d.parentNode.__data__.filterMin == d.parentNode.__data__.min && d.parentNode.__data__.filterMax == d.parentNode.__data__.max)) {
                    brushes[d.parentNode.__data__.brushId].extent([(d.parentNode.__data__.filterMin - d.parentNode.__data__.min) / d.parentNode.__data__.timeStep, (d.parentNode.__data__.filterMax - d.parentNode.__data__.min) / d.parentNode.__data__.timeStep]);
                    setFilterMinMax(d[0], d[0].__data__);
                }
            }

            d3.select(d[0])
                .append("g")
                .attr("class", "x brush")
                .call(brushes[brushes.length - 1])
                .selectAll("rect")
                .attr("y", -4)
                .attr("height", height + 5);

            if ((['date', 'lrgPace', 'smlPace', 'num', 'geo']).indexOf(d3.select(d[0]).data()[0].type) > -1) {
                setFilterMinMax(d[0], d3.select(d[0]).data()[0]);
            }
            //setFilterMinMax(d[0], filterActiveColumns.filter(function(d,i) {
            //  return (d.type=='date' || d.type=='lrgPace' || d.type=='smlPace' || d.type=='num' || d.type=='geo');
            //})[i]);
        });
    setBrushDash();


    //EXIT
    div.exit()
        .remove();

    filterFilters();

    div.sort(function(a, b) {
        return a.displayOrder - b.displayOrder;
    });

    // map filter
    div = d3.select('#filtersContainer')
        .selectAll('div.filterDivMap')
        .data(filterActiveColumns.filter(function(d, i) {
            return (d.type == 'map');
        }), function(d) {
            return d.field;
        });

    var newDiv = div.enter()
        .append('div')
        .classed('filterDivMap', true);

    newDiv.append('p')
        .classed('filterTitle', true)
        .text(function(d) {
            return d.desc;
        });

    newDiv.append('p')
        .attr('id', 'mapFilterText')
        .html(function(d) {
            return 'Top left: ' + f5dp(d.maxLat) + ', ' + f5dp(d.minLng) + '<br/>Bottom right: ' + f5dp(d.minLat) + ', ' + f5dp(d.maxLng);
        });

    newDiv.append('button')
        .classed('closeBtn', true)
        .on('click', function(d) {
            document.getElementById('mapFilterCheckBox').checked = false;
            offset = 0;
            d.filterActive = 0;
            setFilterActive();
            updateActiveFilters();
            updateAvailableFilters();
            dataLoaded();
        })
        .text('X');

    //EXIT
    div.exit()
        .remove();
}

function updateAvailableFilters() {
    var sel = document.getElementById('addFilterSelect');

    while ($('#addFilterSelect option').size() > 0) {
        sel.remove(0);
    }

    var option = d3.select('#addFilterSelect')
        .selectAll('option')
        .data(filterInactiveColumns
            .filter(function(d) {
                return d.display != 'segPRs'
            })
            .sort(function(a, b) {
                if (a.desc.toLowerCase() < b.desc.toLowerCase()) return -1;
                if (a.desc.toLowerCase() > b.desc.toLowerCase()) return 1;
                return 0;
            }));

    //NEW
    option.enter()
        .append('option')
        .text(function(d) {
            return d.desc;
        });

    document.getElementById('addFilterSelect').selectedIndex = 0;
}

d3.select('#addFilterBtn')
    .on('click', function(d) {
        var sel = document.getElementById('addFilterSelect');
        var data = sel[sel.selectedIndex];
        initFilter(data.__data__);
        data.__data__.filterActive = 1;
        applyFilters(a);
        setFilterActive();
        $('#collapseFilter').collapse('show');
        var colBtn = $(this.parentNode.parentNode).find('.collapseBtn')[0];
        setCollapseState(colBtn);
        d3.select(colBtn).classed('collapsed', false);
        dataLoaded(undefined, true);
        updateActiveFilters();
        updateAvailableFilters();
        writeURL();
        window.setTimeout(setMapChartHeight, 1500);
    });

var filterInactiveColumns;

function setFilterActive() {
    filterActiveColumns = new Array();
    filterInactiveColumns = new Array();

    for (i = 0; i < columns.length; i++) {
        if (columns[i].filterActive == 1) {
            filterActiveColumns.push(columns[i]);
        } else {
            filterInactiveColumns.push(columns[i]);
        }
    }
}

$('.fixedHeadTableWrapper table tbody').scroll(function() {
    $('.fixedHeadTableWrapper thead tr').css('left', -1 * $(this).scrollLeft());
})

var tableShownChecked = true;
var mapShownChecked = false;
var chartShownChecked = false;
var photosShownChecked = false;

$('#viewTableCheckBox').click(function() {
    tableShownChecked = !$(this).hasClass('active');
    setCookie(cpref + 't', tableShownChecked, 355);
    setMainControls();
});

$('#viewMapCheckBox').click(function() {
    mapShownChecked = !$(this).hasClass('active');
    setMainControls();

    setCookie(cpref + 'm', mapShownChecked, 355);

    if (typeof(map) === 'undefined') {
        initMap('', true);
    }
    if (mapShownChecked) {
        dataLoaded();
    }
});

$('#viewChartCheckBox').click(function() {
    chartShownChecked = !$(this).hasClass('active');

    setCookie(cpref + 'c', chartShownChecked, 355);

    setMainControls();
    if (chartShownChecked) {
        drawChart();
    }
});

$('#viewPhotosCheckBox').click(function() {
    photosShownChecked = !$(this).hasClass('active');

    setCookie(cpref + 'p', photosShownChecked, 355);

    setMainControls();
    if (photosShownChecked) {
        drawPhotos();
    }
});

$('#rowCount').change(function() {
    rowCount = parseInt($(this).val());
    offset = 0;
    dataLoaded();
})

$('#prevBatch').click(function() {
    offset = Math.max(0, offset - 1);
    dataLoaded();
    return false;
})

$('#nextBatch').click(function() {
    offset = Math.min(Math.floor(applyFilters(a).length / rowCount), offset + 1);
    dataLoaded();
    return false;
})

function setMainControls() {
    if (embed) {
        $('#mapContainer').css('display', 'block');
        $('#tableContainer').css('display', 'block');
    } else {
        if (tableShownChecked && !mapShownChecked && !chartShownChecked && !photosShownChecked) {
            $('#mapChartContainer').css('display', 'none');
            $('#tableContainer').css('display', 'block');
            $('#tableContainer').removeClass('span6');
        }
        if (!tableShownChecked && (mapShownChecked || chartShownChecked || photosShownChecked)) {
            $('#mapChartContainer').css('display', 'block');
            $('#tableContainer').css('display', 'none');
            $('#mapChartContainer').removeClass('span6');
        }
        if (tableShownChecked && (mapShownChecked || chartShownChecked || photosShownChecked)) {
            $('#mapChartContainer').css('display', 'block');
            $('#tableContainer').css('display', 'block');
            $('#mapChartContainer').addClass('span6');
            $('#tableContainer').addClass('span6');
        }

        if (mapShownChecked) {
            $('#mapContainer').css('display', 'block');
        } else {
            $('#mapContainer').css('display', 'none');
        }

        if (chartShownChecked) {
            $('#chartContainer').css('display', 'block');
        } else {
            $('#chartContainer').css('display', 'none');
        }

        if (photosShownChecked) {
            $('#photosContainer').css('display', 'block');
        } else {
            $('#photosContainer').css('display', 'none');
        }

        var h = (mapShownChecked ? 1 : 0) + (chartShownChecked ? 1 : 0) + (photosShownChecked ? 1 : 0);
        h = h == 3 ? '33.33%' : (h == 2 ? '50%' : '100%');

        $('#mapContainer').css('height', h);
        $('#chartContainer').css('height', h);
        $('#photosContainer').css('height', h);

        if (mapShownChecked || chartShownChecked || photosShownChecked) {
            setMapChartHeight();
        }
    }
}

function setMapChartHeight() {
    if (embed) {
        $('#mapChartContainer').height('400px');
        $('#mapDomObj').height(400 - $('#mapOptions').height());
        top.postMessage(d3.select('body').style('height'), 'http://www.' + embedType + '.cc');
    } else {
        if ($('#mapChartContainer').length > 0) {
            $('#mapChartContainer').height(Math.max((mapShownChecked ? 250 : 0) + (chartShownChecked ? 250 : 0) + (photosShownChecked ? 250 : 0), Math.max(350, $(window).height() - $('#dataContainer').offset().top - 20)));
            $('#mapDomObj').height($('#mapContainer').height() - $('#mapOptions').height() - $('#div_photo_ex').height());
            $('#chart').height($('#chartContainer').height());
            $('#chart svg').height($('#chartContainer').height());

            if (typeof(map) !== 'undefined' && map != null) map.invalidateSize();

            //if (document.getElementById('mapAutoZoomCheckBox') != null && document.getElementById('mapAutoZoomCheckBox').checked && document.getElementById('mapFilterCheckBox') != null && !document.getElementById('mapFilterCheckBox').checked) {
            //    setExtent(applyFilters(a));
            //}
            /*if (typeof(map) !== 'undefined') {
                map.updateSize();
                window.setTimeout(map.updateSize, 1000);
            }*/
        }

        if (chartShownChecked) {
            drawChart();
        }
        if (photosShownChecked) {
          drawPhotos();
        }
    }
}

$('#filtersExpander').click(function() {
    setTimeout(function() {
        setMapChartHeight()
    }, 300);
});

function setCollapseState(obj) {
    d3.select(obj).select('i')
        .classed('icon-arrow-down', !d3.select(obj).classed('collapsed'))
        .classed('icon-arrow-up', d3.select(obj).classed('collapsed'));

    d3.select(obj.parentNode)
        .classed('collapsedHeader', d3.select(obj).classed('collapsed'));

    if (embed) {
        window.setTimeout(function() {
            top.postMessage(d3.select('body').style('height'), 'http://www.' + embedType + '.cc');
        }, 1000);
    }
    setTimeout(function() {
        if (typeof(map) !== 'undefined' && map != null) map.invalidateSize();
    }, 1000);
}

d3.selectAll('.collapseBtn').
on('click', function(d) {
    setCollapseState(this);
});

d3.selectAll('.collapseBtn')
    .on('click', function(d) {
        setCollapseState(this);
    });

d3.select('#saveConfigModalBtn')
    .on('click', function(d) {
        popSaveConfig();
    });
d3.selectAll('[name=configOverrideRadio]')
    .on('change', function() {
        setConfigSaveOptions(this)
    });

d3.select('#saveConfigBtn')
    .on('click', function(d) {
        saveConfig();
    });

var resizeTimer;

function resizeBy() {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(function() {
        resize()
    }, 100);
}

function resize() {
    setMapChartHeight();
}

d3.select('#csvExport')
    .on('click', function(d) {
        d3.selectAll('.csvDownload').remove();

        alert('Please note: the data is provided in its raw state (i.e. all in metres and seconds) for you to convert as you require.  If you want the data as it is displayed in the table then copy/paste table contents into your spreadsheet. Might not work in Firefox.');

        var data = applyFilters(a);
        var csvData = [];
        var tmpArr = [];
        var hdrArr = [];
        var tmpStr = '';
        var counter = 1;

        var actF = columns.filter(function(d, i) {
            return d.displayOrder > -1 && d.type != 'prsDist';
        });

        if (cpref == 'r') {
            actF.push({
                title: "Route Id",
                field: "id"
            });
        } else {
            actF.push({
                title: "Activity Id",
                field: "ai"
            });
            if (cpref == 's') {
                actF.push({
                    title: "Segment Id",
                    field: "si"
                });
            }
        }

        for (var j = 0; j < actF.length; j++) {
            tmpStr = actF[j].title;
            hdrArr.push('"' + tmpStr.replace(/\<br\/\>/g, ' ') + '"');
        }
        csvData.push(hdrArr);

        for (var i = 0; i < data.length; i++) {
            tmpArr = [];
            for (var j = 0; j < actF.length; j++) {
                var val = data[i][actF[j].field];
                if (val == null) {
                    tmpArr.push('');
                } else {
                    if (typeof(val) === "number") {
                        tmpArr.push(val);
                    } else {
                        if (typeof(val) === 'boolean') {
                            tmpArr.push(val ? 'yes' : 'no');
                        } else {
                            if (typeof(val.getTime) === 'function') {
                                tmpArr.push(fDate(val));
                            } else {
                                if (val.match(/^-{0,1}\d*\.{0,1}\d+$/)) {
                                    tmpArr.push(parseFloat(val));
                                } else {
                                    tmpStr = val.replace(/"/g, '""');//.replace(/#/g, '');
                                    tmpArr.push('"' + tmpStr + '"');
                                }
                            }
                        }
                    }
                }
            }
            csvData.push(tmpArr.join(','));

            if ((i > 0 && i % 2500 == 0) || i == data.length - 1) {
                var encodedUri = "data:text/csv;charset=utf-8," + encodeURIComponent(csvData.join('\r\n'));

                d3.select('.pager')
                    .append('a')
                    .attr('href', encodedUri)
                    .style('margin-left', '4px')
                    .attr('download', (cpref == 's' ? 'segments' : (cpref == 'r' ? 'routes' : 'activities')) + '.csv')
                    .classed('csvDownload btn btn-primary btn-mini', true)
                    .text('Download CSV ' + counter);

                csvData = [];
                csvData.push(hdrArr);
                counter++;
            }
        }

        return;
        var encodedUri = encodeURI("data:text/csv;charset=utf-8," + csvData.join('\n'));

        d3.select('.pager')
            .append('a')
            .attr('href', encodedUri)
            .style('margin-left', '4px')
            .attr('download', (cpref == 's' ? 'segments' : 'activities') + '.csv')
            .classed('csvDownload btn btn-primary btn-mini', true)
            .text('Download CSV');

        /*var link = document.createElement("a");
        link.setAttribute("href", encodedUri);
        link.setAttribute("download", (cpref == 's' ? 'segments' : 'activities') + '.csv');

        var tn = document.createTextNode("Download CSV");
        link.appendChild(tn);
        d3.select('.pager').node().appendChild(link);*/
        //link.click();
    });
