<commit_before>define(function(require) {

    var zrUtil = require('zrender/core/util');
    var modelUtil = require('../../util/model');
    var modelHelper = require('./modelHelper');
    var findPointFromSeries = require('./findPointFromSeries');

    var each = zrUtil.each;
    var curry = zrUtil.curry;
    var get = modelUtil.makeGetter();

    /**
     * Basic logic: check all axis, if they do not demand show/highlight,
     * then hide/downplay them.
     *
     * @param {Object} coordSysAxesInfo
     * @param {Object} payload
     * @param {string} [payload.currTrigger] 'click' | 'mousemove' | 'leave'
     * @param {Array.<number>} [payload.x] x and y, which are mandatory, specify a point to
     *              trigger axisPointer and tooltip.
     * @param {Array.<number>} [payload.y] x and y, which are mandatory, specify a point to
     *              trigger axisPointer and tooltip.
     * @param {Object} [payload.seriesIndex] finder, optional, restrict target axes.
     * @param {Object} [payload.dataIndex] finder, restrict target axes.
     * @param {Object} [payload.axesInfo] finder, restrict target axes.
     *        [{
     *          axisDim: 'x'|'y'|'angle'|...,
     *          axisIndex: ...,
     *          value: ...
     *        }, ...]
     * @param {Function} [payload.dispatchAction]
     * @param {Object} [payload.tooltipOption]
     * @param {Object|Array.<number>|Function} [payload.position] Tooltip position,
     *        which can be specified in dispatchAction
     * @param {module:echarts/model/Global} ecModel
     * @param {module:echarts/ExtensionAPI} api
     * @return {Object} content of event obj for echarts.connect.
     */
    function axisTrigger(payload, ecModel, api) {
        var currTrigger = payload.currTrigger;
        var point = [payload.x, payload.y];
        var finder = payload;
        var dispatchAction = payload.dispatchAction || zrUtil.bind(api.dispatchAction, api);
        var coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo;

        if (illegalPoint(point)) {
            // Used in the default behavior of `connection`: use the sample seriesIndex
            // and dataIndex. And also used in the tooltipView trigger.
            point = findPointFromSeries({
                seriesIndex: finder.seriesIndex,
                // Do not use dataIndexInside from other ec instance.
                // FIXME: auto detect it?
                dataIndex: finder.dataIndex
            }, ecModel).point;
        }
        var isIllegalPoint = illegalPoint(point);

        // Axis and value can be specified when calling dispatchAction({type: 'updateAxisPointer'}).
        // Notice: In this case, it is difficult to get the `point` (which is necessary to show
        // tooltip, so if point is not given, we just use the point found by sample seriesIndex
        // and dataIndex.
        var inputAxesInfo = finder.axesInfo;

        var axesInfo = coordSysAxesInfo.axesInfo;
        var shouldHide = currTrigger === 'leave' || illegalPoint(point);
        var outputFinder = {};

        var showValueMap = {};
        var dataByCoordSys = {list: [], map: {}};
        var updaters = {
            showPointer: curry(showPointer, showValueMap),
            showTooltip: curry(showTooltip, dataByCoordSys)
        };

        // Process for triggered axes.
        each(coordSysAxesInfo.coordSysMap, function (coordSys, coordSysKey) {
            // If a point given, it must be contained by the coordinate system.
            var coordSysContainsPoint = isIllegalPoint || coordSys.containPoint(point);

            each(coordSysAxesInfo.coordSysAxesInfo[coordSysKey], function (axisInfo, key) {
                var axis = axisInfo.axis;
                var inputAxisInfo = findInputAxisInfo(inputAxesInfo, axisInfo);
                // If no inputAxesInfo, no axis is restricted.
                if (!shouldHide && coordSysContainsPoint && (!inputAxesInfo || inputAxisInfo)) {
                    var val = inputAxisInfo && inputAxisInfo.value;
                    if (val == null && !isIllegalPoint) {
                        val = axis.pointToData(point);
                    }
                    val != null && processOnAxis(axisInfo, val, updaters, false, outputFinder);
                }
            });
        });

        // Process for linked axes.
        var linkTriggers = {};
        each(axesInfo, function (tarAxisInfo, tarKey) {
            var linkGroup = tarAxisInfo.linkGroup;

            // If axis has been triggered in the previous stage, it should not be triggered by link.
            if (linkGroup && !showValueMap[tarKey]) {
                each(linkGroup.axesInfo, function (srcAxisInfo, srcKey) {
                    var srcValItem = showValueMap[srcKey];
                    // If srcValItem exist, source axis is triggered, so link to target axis.
                    if (srcAxisInfo !== tarAxisInfo && srcValItem) {
                        var val = srcValItem.value;
                        linkGroup.mapper && (val = tarAxisInfo.axis.scale.parse(linkGroup.mapper(
                            val, makeMapperParam(srcAxisInfo), makeMapperParam(tarAxisInfo)
                        )));
                        linkTriggers[tarAxisInfo.key] = val;
                    }
                });
            }
        });
        each(linkTriggers, function (val, tarKey) {
            processOnAxis(axesInfo[tarKey], val, updaters, true, outputFinder);
        });

        updateModelActually(showValueMap, axesInfo, outputFinder);
        dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction);
        dispatchHighDownActually(axesInfo, dispatchAction, api);

        return outputFinder;
    }

    function processOnAxis(axisInfo, newValue, updaters, dontSnap, outputFinder) {
        var axis = axisInfo.axis;

        if (axis.scale.isBlank() || !axis.containData(newValue)) {
            return;
        }

        if (!axisInfo.involveSeries) {
            updaters.showPointer(axisInfo, newValue);
            return;
        }

        // Heavy calculation. So put it after axis.containData checking.
        var payloadInfo = buildPayloadsBySeries(newValue, axisInfo);
        var payloadBatch = payloadInfo.payloadBatch;
        var snapToValue = payloadInfo.snapToValue;

        // Fill content of event obj for echarts.connect.
        // By defualt use the first involved series data as a sample to connect.
        if (payloadBatch[0] && outputFinder.seriesIndex == null) {
            zrUtil.extend(outputFinder, payloadBatch[0]);
        }

        // If no linkSource input, this process is for collecting link
        // target, where snap should not be accepted.
        if (!dontSnap && axisInfo.snap) {
            if (axis.containData(snapToValue) && snapToValue != null) {
                newValue = snapToValue;
            }
        }

        updaters.showPointer(axisInfo, newValue, payloadBatch, outputFinder);
        // Tooltip should always be snapToValue, otherwise there will be
        // incorrect "axis value ~ series value" mapping displayed in tooltip.
        updaters.showTooltip(axisInfo, payloadInfo, snapToValue);
    }

    function buildPayloadsBySeries(value, axisInfo) {
        var axis = axisInfo.axis;
        var dim = axis.dim;
        var snapToValue = value;
        var payloadBatch = [];
        var minDist = Number.MAX_VALUE;
        var minDiff = -1;

        each(axisInfo.seriesModels, function (series, idx) {
            var dataDim = series.coordDimToDataDim(dim);
            var seriesNestestValue;
            var dataIndices;

            if (series.getAxisTooltipData) {
                var result = series.getAxisTooltipData(dataDim, value, axis);
                dataIndices = result.dataIndices;
                seriesNestestValue = result.nestestValue;
            }
            else {
                dataIndices = series.getData().indicesOfNearest(
                    dataDim[0],
                    value,
                    // Add a threshold to avoid find the wrong dataIndex
                    // when data length is not same.
                    false, axis.type === 'category' ? 0.5 : null
                );
                if (!dataIndices.length) {
                    return;
                }
                seriesNestestValue = series.getData().get(dataDim[0], dataIndices[0]);
            }

            if (seriesNestestValue == null || !isFinite(seriesNestestValue)) {
                return;
            }

            var diff = value - seriesNestestValue;
            var dist = Math.abs(diff);
            // Consider category case
            if (dist <= minDist) {
                if (dist < minDist || (diff >= 0 && minDiff < 0)) {
                    minDist = dist;
                    minDiff = diff;
                    snapToValue = seriesNestestValue;
                    payloadBatch.length = 0;
                }
                each(dataIndices, function (dataIndex) {
                    payloadBatch.push({
                        seriesIndex: series.seriesIndex,
                        dataIndexInside: dataIndex,
                        dataIndex: series.getData().getRawIndex(dataIndex)
                    });
                });
            }
        });

        return {
            payloadBatch: payloadBatch,
            snapToValue: snapToValue
        };
    }

    function showPointer(showValueMap, axisInfo, value, payloadBatch) {
        showValueMap[axisInfo.key] = {value: value, payloadBatch: payloadBatch};
    }

    function showTooltip(dataByCoordSys, axisInfo, payloadInfo, value) {
        var payloadBatch = payloadInfo.payloadBatch;
        var axis = axisInfo.axis;
        var axisModel = axis.model;
        var axisPointerModel = axisInfo.axisPointerModel;

        // If no data, do not create anything in dataByCoordSys,
        // whose length will be used to judge whether dispatch action.
        if (!axisInfo.triggerTooltip || !payloadBatch.length) {
            return;
        }

        var coordSysModel = axisInfo.coordSys.model;
        var coordSysKey = modelHelper.makeKey(coordSysModel);
        var coordSysItem = dataByCoordSys.map[coordSysKey];
        if (!coordSysItem) {
            coordSysItem = dataByCoordSys.map[coordSysKey] = {
                coordSysId: coordSysModel.id,
                coordSysIndex: coordSysModel.componentIndex,
                coordSysType: coordSysModel.type,
                coordSysMainType: coordSysModel.mainType,
                dataByAxis: []
            };
            dataByCoordSys.list.push(coordSysItem);
        }

        coordSysItem.dataByAxis.push({
            axisDim: axis.dim,
            axisIndex: axisModel.componentIndex,
            axisType: axisModel.type,
            axisId: axisModel.id,
            value: value,
            // Caustion: viewHelper.getValueLabel is actually on "view stage", which
            // depends that all models have been updated. So it should not be performed
            // here. Considering axisPointerModel used here is volatile, which is hard
            // to be retrieve in TooltipView, we prepare parameters here.
            valueLabelOpt: {
                precision: axisPointerModel.get('label.precision'),
                formatter: axisPointerModel.get('label.formatter')
            },
            seriesDataIndices: payloadBatch.slice()
        });
    }

    function updateModelActually(showValueMap, axesInfo, outputFinder) {
        var outputAxesInfo = outputFinder.axesInfo = [];
        // Basic logic: If no 'show' required, 'hide' this axisPointer.
        each(axesInfo, function (axisInfo, key) {
            var option = axisInfo.axisPointerModel.option;
            var valItem = showValueMap[key];

            if (valItem) {
                !axisInfo.useHandle && (option.status = 'show');
                option.value = valItem.value;
                // For label formatter param and highlight.
                option.seriesDataIndices = (valItem.payloadBatch || []).slice();
            }
            // When always show (e.g., handle used), remain
            // original value and status.
            else {
                // If hide, value still need to be set, consider
                // click legend to toggle axis blank.
                !axisInfo.useHandle && (option.status = 'hide');
            }

            // If status is 'hide', should be no info in payload.
            option.status === 'show' && outputAxesInfo.push({
                axisDim: axisInfo.axis.dim,
                axisIndex: axisInfo.axis.model.componentIndex,
                value: option.value
            });
        });
    }

    function dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction) {
        // Basic logic: If no showTip required, hideTip will be dispatched.
        if (illegalPoint(point) || !dataByCoordSys.list.length) {
            dispatchAction({type: 'hideTip'});
            return;
        }

        // In most case only one axis (or event one series is used). It is
        // convinient to fetch payload.seriesIndex and payload.dataIndex
        // dirtectly. So put the first seriesIndex and dataIndex of the first
        // axis on the payload.
        var sampleItem = ((dataByCoordSys.list[0].dataByAxis[0] || {}).seriesDataIndices || [])[0] || {};

        dispatchAction({
            type: 'showTip',
            escapeConnect: true,
            x: point[0],
            y: point[1],
            tooltipOption: payload.tooltipOption,
            position: payload.position,
            dataIndexInside: sampleItem.dataIndexInside,
            dataIndex: sampleItem.dataIndex,
            seriesIndex: sampleItem.seriesIndex,
            dataByCoordSys: dataByCoordSys.list
        });
    }

    function dispatchHighDownActually(axesInfo, dispatchAction, api) {
        // FIXME
        // highlight status modification shoule be a stage of main process?
        // (Consider confilct (e.g., legend and axisPointer) and setOption)

        var zr = api.getZr();
        var highDownKey = 'axisPointerLastHighlights';
        var lastHighlights = get(zr)[highDownKey] || {};
        var newHighlights = get(zr)[highDownKey] = {};

        // Update highlight/downplay status according to axisPointer model.
        // Build hash map and remove duplicate incidentally.
        each(axesInfo, function (axisInfo, key) {
            var option = axisInfo.axisPointerModel.option;
            option.status === 'show' && each(option.seriesDataIndices, function (batchItem) {
                var key = batchItem.seriesIndex + ' | ' + batchItem.dataIndex;
                newHighlights[key] = batchItem;
            });
        });

        // Diff.
        var toHighlight = [];
        var toDownplay = [];
        zrUtil.each(lastHighlights, function (batchItem, key) {
            !newHighlights[key] && toDownplay.push(batchItem);
        });
        zrUtil.each(newHighlights, function (batchItem, key) {
            !lastHighlights[key] && toHighlight.push(batchItem);
        });

        toDownplay.length && api.dispatchAction({
            type: 'downplay', escapeConnect: true, batch: toDownplay
        });
        toHighlight.length && api.dispatchAction({
            type: 'highlight', escapeConnect: true, batch: toHighlight
        });
    }

    function findInputAxisInfo(inputAxesInfo, axisInfo) {
        for (var i = 0; i < (inputAxesInfo || []).length; i++) {
            var inputAxisInfo = inputAxesInfo[i];
            if (axisInfo.axis.dim === inputAxisInfo.axisDim
                && axisInfo.axis.model.componentIndex === inputAxisInfo.axisIndex
            ) {
                return inputAxisInfo;
            }
        }
    }

    function makeMapperParam(axisInfo) {
        var axisModel = axisInfo.axis.model;
        var item = {};
        var dim = item.axisDim = axisInfo.axis.dim;
        item.axisIndex = item[dim + 'AxisIndex'] = axisModel.componentIndex;
        item.axisName = item[dim + 'AxisName'] = axisModel.name;
        item.axisId = item[dim + 'AxisId'] = axisModel.id;
        return item;
    }

    function illegalPoint(point) {
        return !point || point[0] == null || isNaN(point[0]) || point[1] == null || isNaN(point[1]);
    }

    return axisTrigger;
});<commit_msg>Try to Fix #6121. (axesInfo of null)<commit_after>define(function(require) {

    var zrUtil = require('zrender/core/util');
    var modelUtil = require('../../util/model');
    var modelHelper = require('./modelHelper');
    var findPointFromSeries = require('./findPointFromSeries');

    var each = zrUtil.each;
    var curry = zrUtil.curry;
    var get = modelUtil.makeGetter();

    /**
     * Basic logic: check all axis, if they do not demand show/highlight,
     * then hide/downplay them.
     *
     * @param {Object} coordSysAxesInfo
     * @param {Object} payload
     * @param {string} [payload.currTrigger] 'click' | 'mousemove' | 'leave'
     * @param {Array.<number>} [payload.x] x and y, which are mandatory, specify a point to
     *              trigger axisPointer and tooltip.
     * @param {Array.<number>} [payload.y] x and y, which are mandatory, specify a point to
     *              trigger axisPointer and tooltip.
     * @param {Object} [payload.seriesIndex] finder, optional, restrict target axes.
     * @param {Object} [payload.dataIndex] finder, restrict target axes.
     * @param {Object} [payload.axesInfo] finder, restrict target axes.
     *        [{
     *          axisDim: 'x'|'y'|'angle'|...,
     *          axisIndex: ...,
     *          value: ...
     *        }, ...]
     * @param {Function} [payload.dispatchAction]
     * @param {Object} [payload.tooltipOption]
     * @param {Object|Array.<number>|Function} [payload.position] Tooltip position,
     *        which can be specified in dispatchAction
     * @param {module:echarts/model/Global} ecModel
     * @param {module:echarts/ExtensionAPI} api
     * @return {Object} content of event obj for echarts.connect.
     */
    function axisTrigger(payload, ecModel, api) {
        var currTrigger = payload.currTrigger;
        var point = [payload.x, payload.y];
        var finder = payload;
        var dispatchAction = payload.dispatchAction || zrUtil.bind(api.dispatchAction, api);
        var coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo;

        // Pending
        // See #6121. But we are not able to reproduce it yet.
        if (!coordSysAxesInfo) {
            return;
        }

        if (illegalPoint(point)) {
            // Used in the default behavior of `connection`: use the sample seriesIndex
            // and dataIndex. And also used in the tooltipView trigger.
            point = findPointFromSeries({
                seriesIndex: finder.seriesIndex,
                // Do not use dataIndexInside from other ec instance.
                // FIXME: auto detect it?
                dataIndex: finder.dataIndex
            }, ecModel).point;
        }
        var isIllegalPoint = illegalPoint(point);

        // Axis and value can be specified when calling dispatchAction({type: 'updateAxisPointer'}).
        // Notice: In this case, it is difficult to get the `point` (which is necessary to show
        // tooltip, so if point is not given, we just use the point found by sample seriesIndex
        // and dataIndex.
        var inputAxesInfo = finder.axesInfo;

        var axesInfo = coordSysAxesInfo.axesInfo;
        var shouldHide = currTrigger === 'leave' || illegalPoint(point);
        var outputFinder = {};

        var showValueMap = {};
        var dataByCoordSys = {list: [], map: {}};
        var updaters = {
            showPointer: curry(showPointer, showValueMap),
            showTooltip: curry(showTooltip, dataByCoordSys)
        };

        // Process for triggered axes.
        each(coordSysAxesInfo.coordSysMap, function (coordSys, coordSysKey) {
            // If a point given, it must be contained by the coordinate system.
            var coordSysContainsPoint = isIllegalPoint || coordSys.containPoint(point);

            each(coordSysAxesInfo.coordSysAxesInfo[coordSysKey], function (axisInfo, key) {
                var axis = axisInfo.axis;
                var inputAxisInfo = findInputAxisInfo(inputAxesInfo, axisInfo);
                // If no inputAxesInfo, no axis is restricted.
                if (!shouldHide && coordSysContainsPoint && (!inputAxesInfo || inputAxisInfo)) {
                    var val = inputAxisInfo && inputAxisInfo.value;
                    if (val == null && !isIllegalPoint) {
                        val = axis.pointToData(point);
                    }
                    val != null && processOnAxis(axisInfo, val, updaters, false, outputFinder);
                }
            });
        });

        // Process for linked axes.
        var linkTriggers = {};
        each(axesInfo, function (tarAxisInfo, tarKey) {
            var linkGroup = tarAxisInfo.linkGroup;

            // If axis has been triggered in the previous stage, it should not be triggered by link.
            if (linkGroup && !showValueMap[tarKey]) {
                each(linkGroup.axesInfo, function (srcAxisInfo, srcKey) {
                    var srcValItem = showValueMap[srcKey];
                    // If srcValItem exist, source axis is triggered, so link to target axis.
                    if (srcAxisInfo !== tarAxisInfo && srcValItem) {
                        var val = srcValItem.value;
                        linkGroup.mapper && (val = tarAxisInfo.axis.scale.parse(linkGroup.mapper(
                            val, makeMapperParam(srcAxisInfo), makeMapperParam(tarAxisInfo)
                        )));
                        linkTriggers[tarAxisInfo.key] = val;
                    }
                });
            }
        });
        each(linkTriggers, function (val, tarKey) {
            processOnAxis(axesInfo[tarKey], val, updaters, true, outputFinder);
        });

        updateModelActually(showValueMap, axesInfo, outputFinder);
        dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction);
        dispatchHighDownActually(axesInfo, dispatchAction, api);

        return outputFinder;
    }

    function processOnAxis(axisInfo, newValue, updaters, dontSnap, outputFinder) {
        var axis = axisInfo.axis;

        if (axis.scale.isBlank() || !axis.containData(newValue)) {
            return;
        }

        if (!axisInfo.involveSeries) {
            updaters.showPointer(axisInfo, newValue);
            return;
        }

        // Heavy calculation. So put it after axis.containData checking.
        var payloadInfo = buildPayloadsBySeries(newValue, axisInfo);
        var payloadBatch = payloadInfo.payloadBatch;
        var snapToValue = payloadInfo.snapToValue;

        // Fill content of event obj for echarts.connect.
        // By defualt use the first involved series data as a sample to connect.
        if (payloadBatch[0] && outputFinder.seriesIndex == null) {
            zrUtil.extend(outputFinder, payloadBatch[0]);
        }

        // If no linkSource input, this process is for collecting link
        // target, where snap should not be accepted.
        if (!dontSnap && axisInfo.snap) {
            if (axis.containData(snapToValue) && snapToValue != null) {
                newValue = snapToValue;
            }
        }

        updaters.showPointer(axisInfo, newValue, payloadBatch, outputFinder);
        // Tooltip should always be snapToValue, otherwise there will be
        // incorrect "axis value ~ series value" mapping displayed in tooltip.
        updaters.showTooltip(axisInfo, payloadInfo, snapToValue);
    }

    function buildPayloadsBySeries(value, axisInfo) {
        var axis = axisInfo.axis;
        var dim = axis.dim;
        var snapToValue = value;
        var payloadBatch = [];
        var minDist = Number.MAX_VALUE;
        var minDiff = -1;

        each(axisInfo.seriesModels, function (series, idx) {
            var dataDim = series.coordDimToDataDim(dim);
            var seriesNestestValue;
            var dataIndices;

            if (series.getAxisTooltipData) {
                var result = series.getAxisTooltipData(dataDim, value, axis);
                dataIndices = result.dataIndices;
                seriesNestestValue = result.nestestValue;
            }
            else {
                dataIndices = series.getData().indicesOfNearest(
                    dataDim[0],
                    value,
                    // Add a threshold to avoid find the wrong dataIndex
                    // when data length is not same.
                    false, axis.type === 'category' ? 0.5 : null
                );
                if (!dataIndices.length) {
                    return;
                }
                seriesNestestValue = series.getData().get(dataDim[0], dataIndices[0]);
            }

            if (seriesNestestValue == null || !isFinite(seriesNestestValue)) {
                return;
            }

            var diff = value - seriesNestestValue;
            var dist = Math.abs(diff);
            // Consider category case
            if (dist <= minDist) {
                if (dist < minDist || (diff >= 0 && minDiff < 0)) {
                    minDist = dist;
                    minDiff = diff;
                    snapToValue = seriesNestestValue;
                    payloadBatch.length = 0;
                }
                each(dataIndices, function (dataIndex) {
                    payloadBatch.push({
                        seriesIndex: series.seriesIndex,
                        dataIndexInside: dataIndex,
                        dataIndex: series.getData().getRawIndex(dataIndex)
                    });
                });
            }
        });

        return {
            payloadBatch: payloadBatch,
            snapToValue: snapToValue
        };
    }

    function showPointer(showValueMap, axisInfo, value, payloadBatch) {
        showValueMap[axisInfo.key] = {value: value, payloadBatch: payloadBatch};
    }

    function showTooltip(dataByCoordSys, axisInfo, payloadInfo, value) {
        var payloadBatch = payloadInfo.payloadBatch;
        var axis = axisInfo.axis;
        var axisModel = axis.model;
        var axisPointerModel = axisInfo.axisPointerModel;

        // If no data, do not create anything in dataByCoordSys,
        // whose length will be used to judge whether dispatch action.
        if (!axisInfo.triggerTooltip || !payloadBatch.length) {
            return;
        }

        var coordSysModel = axisInfo.coordSys.model;
        var coordSysKey = modelHelper.makeKey(coordSysModel);
        var coordSysItem = dataByCoordSys.map[coordSysKey];
        if (!coordSysItem) {
            coordSysItem = dataByCoordSys.map[coordSysKey] = {
                coordSysId: coordSysModel.id,
                coordSysIndex: coordSysModel.componentIndex,
                coordSysType: coordSysModel.type,
                coordSysMainType: coordSysModel.mainType,
                dataByAxis: []
            };
            dataByCoordSys.list.push(coordSysItem);
        }

        coordSysItem.dataByAxis.push({
            axisDim: axis.dim,
            axisIndex: axisModel.componentIndex,
            axisType: axisModel.type,
            axisId: axisModel.id,
            value: value,
            // Caustion: viewHelper.getValueLabel is actually on "view stage", which
            // depends that all models have been updated. So it should not be performed
            // here. Considering axisPointerModel used here is volatile, which is hard
            // to be retrieve in TooltipView, we prepare parameters here.
            valueLabelOpt: {
                precision: axisPointerModel.get('label.precision'),
                formatter: axisPointerModel.get('label.formatter')
            },
            seriesDataIndices: payloadBatch.slice()
        });
    }

    function updateModelActually(showValueMap, axesInfo, outputFinder) {
        var outputAxesInfo = outputFinder.axesInfo = [];
        // Basic logic: If no 'show' required, 'hide' this axisPointer.
        each(axesInfo, function (axisInfo, key) {
            var option = axisInfo.axisPointerModel.option;
            var valItem = showValueMap[key];

            if (valItem) {
                !axisInfo.useHandle && (option.status = 'show');
                option.value = valItem.value;
                // For label formatter param and highlight.
                option.seriesDataIndices = (valItem.payloadBatch || []).slice();
            }
            // When always show (e.g., handle used), remain
            // original value and status.
            else {
                // If hide, value still need to be set, consider
                // click legend to toggle axis blank.
                !axisInfo.useHandle && (option.status = 'hide');
            }

            // If status is 'hide', should be no info in payload.
            option.status === 'show' && outputAxesInfo.push({
                axisDim: axisInfo.axis.dim,
                axisIndex: axisInfo.axis.model.componentIndex,
                value: option.value
            });
        });
    }

    function dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction) {
        // Basic logic: If no showTip required, hideTip will be dispatched.
        if (illegalPoint(point) || !dataByCoordSys.list.length) {
            dispatchAction({type: 'hideTip'});
            return;
        }

        // In most case only one axis (or event one series is used). It is
        // convinient to fetch payload.seriesIndex and payload.dataIndex
        // dirtectly. So put the first seriesIndex and dataIndex of the first
        // axis on the payload.
        var sampleItem = ((dataByCoordSys.list[0].dataByAxis[0] || {}).seriesDataIndices || [])[0] || {};

        dispatchAction({
            type: 'showTip',
            escapeConnect: true,
            x: point[0],
            y: point[1],
            tooltipOption: payload.tooltipOption,
            position: payload.position,
            dataIndexInside: sampleItem.dataIndexInside,
            dataIndex: sampleItem.dataIndex,
            seriesIndex: sampleItem.seriesIndex,
            dataByCoordSys: dataByCoordSys.list
        });
    }

    function dispatchHighDownActually(axesInfo, dispatchAction, api) {
        // FIXME
        // highlight status modification shoule be a stage of main process?
        // (Consider confilct (e.g., legend and axisPointer) and setOption)

        var zr = api.getZr();
        var highDownKey = 'axisPointerLastHighlights';
        var lastHighlights = get(zr)[highDownKey] || {};
        var newHighlights = get(zr)[highDownKey] = {};

        // Update highlight/downplay status according to axisPointer model.
        // Build hash map and remove duplicate incidentally.
        each(axesInfo, function (axisInfo, key) {
            var option = axisInfo.axisPointerModel.option;
            option.status === 'show' && each(option.seriesDataIndices, function (batchItem) {
                var key = batchItem.seriesIndex + ' | ' + batchItem.dataIndex;
                newHighlights[key] = batchItem;
            });
        });

        // Diff.
        var toHighlight = [];
        var toDownplay = [];
        zrUtil.each(lastHighlights, function (batchItem, key) {
            !newHighlights[key] && toDownplay.push(batchItem);
        });
        zrUtil.each(newHighlights, function (batchItem, key) {
            !lastHighlights[key] && toHighlight.push(batchItem);
        });

        toDownplay.length && api.dispatchAction({
            type: 'downplay', escapeConnect: true, batch: toDownplay
        });
        toHighlight.length && api.dispatchAction({
            type: 'highlight', escapeConnect: true, batch: toHighlight
        });
    }

    function findInputAxisInfo(inputAxesInfo, axisInfo) {
        for (var i = 0; i < (inputAxesInfo || []).length; i++) {
            var inputAxisInfo = inputAxesInfo[i];
            if (axisInfo.axis.dim === inputAxisInfo.axisDim
                && axisInfo.axis.model.componentIndex === inputAxisInfo.axisIndex
            ) {
                return inputAxisInfo;
            }
        }
    }

    function makeMapperParam(axisInfo) {
        var axisModel = axisInfo.axis.model;
        var item = {};
        var dim = item.axisDim = axisInfo.axis.dim;
        item.axisIndex = item[dim + 'AxisIndex'] = axisModel.componentIndex;
        item.axisName = item[dim + 'AxisName'] = axisModel.name;
        item.axisId = item[dim + 'AxisId'] = axisModel.id;
        return item;
    }

    function illegalPoint(point) {
        return !point || point[0] == null || isNaN(point[0]) || point[1] == null || isNaN(point[1]);
    }

    return axisTrigger;
});<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { SingleDatePicker } from 'react-dates';
import moment from 'moment';

/**
 * DatePickerRange is a tailor made component designed for selecting
 * a single day off of a calendar.
 *
 * The DatePicker integrates well with the Python datetime module with the
 * startDate and endDate being returned in a string format suitable for
 * creating datetime objects.
 *
 * This component is based off of Airbnb's react-dates react component
 * which can be found here: https://github.com/airbnb/react-dates
 */
export default class DatePickerSingle extends Component {
  constructor(props) {
    super(props);
    const propObj = {
      date: this.props.date,
      initialVisibleMonth: this.props.initial_visible_month,
      minDateAllowed: this.props.min_date_allowed,
      maxDateAllowed: this.props.max_date_allowed
    };
    const momentProps = this.convertPropsToMoment(propObj);
    this.state = {
      date: momentProps.date,
      focused: props.autoFocus,
      initialVisibleMonth: momentProps.initialVisibleMonth,
      minDateAllowed: momentProps.min,
      maxDateAllowed: momentProps.max,
      prevInitialVisibleMonth: props.initial_visible_month,
      prevMinDateAllowed: props.min_date_allowed,
      prevMaxDateAllowed: props.max_date_allowed
    };
  }

  convertPropsToMoment(props) {
    let date = null;
    let initialVisibleMonth = moment(props.date);
    if (typeof props.date !== 'undefined') {
      date = moment(props.date);
    }

    if (typeof props.initialVisibleMonth !== 'undefined') {
      initialVisibleMonth = moment(props.initialVisibleMonth);
    }

    let min; let max;
    if (typeof props.minDateAllowed !== 'undefined') {
      min = moment(props.minDateAllowed);
    }

    if (typeof props.maxDateAllowed !== 'undefined') {
      max = moment(props.maxDateAllowed);
    }

    return { date, initialVisibleMonth, min, max };
  }

  componentWillReceiveProps(newProps) {
    const propObj = {
      date: newProps.date,
      initialVisibleMonth: newProps.initial_visible_month,
      minDateAllowed: newProps.min_date_allowed,
      maxDateAllowed: newProps.max_date_allowed
    };
    const momentProps = this.convertPropsToMoment(propObj);
    if (this.state.date !== momentProps.date) {
      this.setState({ date: momentProps.date });
    }

    if (this.state.prevInitialVisibleMonth !== newProps.initial_visible_month) {
      this.setState({
          prevInitialVisibleMonth: newProps.initial_visible_month,
          initialVisibleMonth: momentProps.initialVisibleMonth
        });
    }

    if (this.state.prevMinDateAllowed !== newProps.min_date_allowed) {
      this.setState({
        prevMinDateAllowed: newProps.min_date_allowed,
        minDateAllowed: momentProps.minDateAllowed
      });
    }

    if (this.state.prevMaxDateAllowed !== newProps.max_date_allowed) {
      this.setState({
        prevMaxDateAllowed: newProps.max_date_allowed,
        maxDateAllowed: momentProps.maxDateAllowed
      });
    }
  }

  render() {
    const { setProps, fireEvent } = this.props;
    let verticalFlag = true;
    if (this.props.calendar_orientation === 'vertical') {
      verticalFlag = false;
    }

    return (
      <SingleDatePicker
        date={this.state.date}
        onDateChange={(date) => {
            this.setState({ date });
            if (date !== null) {
              const dateStr = date.format('YYYY-MM-DD');
              if (setProps) {
                setProps({
                  date: dateStr
                });
              }
              if (fireEvent) {
                fireEvent('change');
              }
            }
          }
        }
        focused={this.state.focused}
        onFocusChange={({ focused }) => this.setState({ focused })}
        initialVisibleMonth={() => {
          if (this.state.date !== null) {
            return this.state.date;
          } else {
            return this.state.initialVisibleMonth;
          }
        }
        }
        isOutsideRange={(date) => {
          if (typeof this.state.minDateAllowed !== 'undefined' &&
              typeof this.state.maxDateAllowed !== 'undefined') {
            return date < this.state.minDateAllowed || date >= this.state.maxDateAllowed;
          } else if (typeof this.state.minDateAllowed === 'undefined' &&
                     typeof this.state.maxDateAllowed !== 'undefined') {
            return date >= this.state.maxDateAllowed;
          } else if (typeof this.state.minDateAllowed !== 'undefined' &&
                     typeof this.state.maxDateAllowed === 'undefined') {
            return date < this.state.minDateAllowed;
          } else {
            return false;
          }
        }
        }
        numberOfMonths={this.props.number_of_months_shown}
        withPortal={this.props.with_portal && verticalFlag}
        withFullScreenPortal={this.props.with_full_screen_portal && verticalFlag}
        firstDayOfWeek={this.props.first_day_of_week}
        enableOutSideDays={this.props.show_outside_days}
        monthFormat={this.props.month_format}
        displayFormat={this.props.display_format}
        placeholder={this.props.placeholder}
        showClearDate={this.props.clearable}
        disabled={this.props.disabled}
        keepOpenOnDateSelect={this.props.stay_open_on_select}
        reopenPickerOnClearDates={this.props.reopen_calendar_on_clear}
        isRTL={this.props.is_RTL}
        orientation={this.props.calendar_orientation}
        daySize={this.props.day_size}
      />
    );
  }
}

DatePickerSingle.propTypes = {
    id: PropTypes.string,

    /**
     * Specifies the starting date for the component, best practice is to pass
     * value via datetime object
     */
    date: PropTypes.string,

    /**
     * Specifies the lowest selectable date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    min_date_allowed: PropTypes.string,

    /**
     * Specifies the highest selectable date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    max_date_allowed: PropTypes.string,

    /**
     * Specifies the month that is initially presented when the user
     * opens the calendar. Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     *
     */
    initial_visible_month: PropTypes.string,

    /**
     * Size of rendered calendar days, higher number
     * means bigger day size and larger calendar overall
     */
    day_size: PropTypes.number,

    /**
     * Orientation of calendar, either vertical or horizontal.
     * Valid options are 'vertical' or 'horizontal'.
     */
    calendar_orientation: PropTypes.oneOf(['vertical', 'horizontal']),

    /**
     * Determines whether the calendar and days operate
     * from left to right or from right to left
     */
    is_RTL: PropTypes.bool,
    /**
     * Text that will be displayed in the input
     * box of the date picker when no date is selected.
     * Default value is 'Start Date'
     */
    placeholder: PropTypes.string,

    /**
     * If True, the calendar will automatically open when cleared
     */
    reopen_calendar_on_clear: PropTypes.bool,

    /**
    * Number of calendar months that are shown when calendar is opened
    */
    number_of_months_shown: PropTypes.number,

    /**
    * If True, calendar will open in a screen overlay portal
    */
    with_portal: PropTypes.bool,

    /**
    * If True, calendar will open in a full screen overlay portal, will
    * take precedent over 'withPortal' if both are set to true
    */
    with_full_screen_portal: PropTypes.bool,

    /**
     * Specifies what day is the first day of the week, values must be
     * from [0, ..., 6] with 0 denoting Sunday and 6 denoting Saturday
     */
    first_day_of_week: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6]),

    /**
    * If True the calendar will not close when the user has selected a value
    * and will wait until the user clicks off the calendar
    */
    stay_open_on_select: PropTypes.bool,

    /**
    * If True the calendar will display days that rollover into
    * the next month
    */
    show_outside_days: PropTypes.bool,

    /**
     * Specifies the format that the month will be displayed in the calendar,
     * valid formats are variations of "MM YY". For example:
     * "MM YY" renders as '05 97' for May 1997
     * "MMMM, YYYY" renders as 'May, 1997' for May 1997
     * "MMM, YY" renders as 'Sep, 97' for September 1997
     */
    month_format: PropTypes.string,

    /**
     * Specifies the format that the selected dates will be displayed
     * valid formats are variations of "MM YY DD". For example:
     * "MM YY DD" renders as '05 10 97' for May 10th 1997
     * "MMMM, YY" renders as 'May, 1997' for May 10th 1997
     * "M, D, YYYY" renders as '07, 10, 1997' for September 10th 1997
     * "MMMM" renders as 'May' for May 10 1997
     */
    display_format: PropTypes.string,

    /**
    * If True, no dates can be selected.
    */
    disabled: PropTypes.bool,

    /**
    * If True, there will be a button that allows for clearing the dates
    */
    clearable: PropTypes.bool,

    /**
    * Dash-assigned callback that gets fired when the value changes.
    */
    setProps: PropTypes.func,

    /**
    * Dash-assigned callback that gets fired when the value changes.
    */
    dashEvents: PropTypes.oneOf(['change'])
  };

DatePickerSingle.defaultProps = {
  calendar_orientation: 'horizontal',
  is_RTL: false,
  day_size: 39,
  with_portal: false,
  with_full_screen_portal: false,
  show_outside_days: true,
  first_day_of_week: 0,
  number_of_months_shown: 1,
  stay_open_on_select: false,
  reopen_calendar_on_clear: false,
  clearable: false,
  disabled: false
};
<commit_msg>Update DatePickerSingle.react.js<commit_after>import React, { Component, PropTypes } from 'react';
import { SingleDatePicker } from 'react-dates';
import moment from 'moment';

/**
 * DatePickerRange is a tailor made component designed for selecting
 * a single day off of a calendar.
 *
 * The DatePicker integrates well with the Python datetime module with the
 * startDate and endDate being returned in a string format suitable for
 * creating datetime objects.
 *
 * This component is based off of Airbnb's react-dates react component
 * which can be found here: https://github.com/airbnb/react-dates
 */
export default class DatePickerSingle extends Component {
  constructor(props) {
    super(props);
    const propObj = {
      date: this.props.date,
      initialVisibleMonth: this.props.initial_visible_month,
      minDateAllowed: this.props.min_date_allowed,
      maxDateAllowed: this.props.max_date_allowed
    };
    const momentProps = this.convertPropsToMoment(propObj);
    this.state = {
      date: momentProps.date,
      focused: props.autoFocus,
      initialVisibleMonth: momentProps.initialVisibleMonth,
      minDateAllowed: momentProps.min,
      maxDateAllowed: momentProps.max,
      prevInitialVisibleMonth: props.initial_visible_month,
      prevMinDateAllowed: props.min_date_allowed,
      prevMaxDateAllowed: props.max_date_allowed
    };
  }

  convertPropsToMoment(props) {
    let date = null;
    let initialVisibleMonth = moment(props.date);
    if (typeof props.date !== 'undefined') {
      date = moment(props.date);
    }

    if (typeof props.initialVisibleMonth !== 'undefined') {
      initialVisibleMonth = moment(props.initialVisibleMonth);
    }

    let min; let max;
    if (typeof props.minDateAllowed !== 'undefined') {
      min = moment(props.minDateAllowed);
    }

    if (typeof props.maxDateAllowed !== 'undefined') {
      max = moment(props.maxDateAllowed);
    }

    return { date, initialVisibleMonth, min, max };
  }

  componentWillReceiveProps(newProps) {
    const propObj = {
      date: newProps.date,
      initialVisibleMonth: newProps.initial_visible_month,
      minDateAllowed: newProps.min_date_allowed,
      maxDateAllowed: newProps.max_date_allowed
    };
    const momentProps = this.convertPropsToMoment(propObj);
    if (this.state.date !== momentProps.date) {
      this.setState({ date: momentProps.date });
    }

    if (this.state.prevInitialVisibleMonth !== newProps.initial_visible_month) {
      this.setState({
          prevInitialVisibleMonth: newProps.initial_visible_month,
          initialVisibleMonth: momentProps.initialVisibleMonth
        });
    }

    if (this.state.prevMinDateAllowed !== newProps.min_date_allowed) {
      this.setState({
        prevMinDateAllowed: newProps.min_date_allowed,
        minDateAllowed: momentProps.minDateAllowed
      });
    }

    if (this.state.prevMaxDateAllowed !== newProps.max_date_allowed) {
      this.setState({
        prevMaxDateAllowed: newProps.max_date_allowed,
        maxDateAllowed: momentProps.maxDateAllowed
      });
    }
  }

  render() {
    const { setProps, fireEvent } = this.props;
    let verticalFlag = true;
    if (this.props.calendar_orientation === 'vertical') {
      verticalFlag = false;
    }

    return (
      <SingleDatePicker
        date={this.state.date}
        onDateChange={(date) => {
            this.setState({ date });
            if (date !== null) {
              const dateStr = date.format('YYYY-MM-DD');
              if (setProps) {
                setProps({
                  date: dateStr
                });
              }
              if (fireEvent) {
                fireEvent('change');
              }
            }
          }
        }
        focused={this.state.focused}
        onFocusChange={({ focused }) => this.setState({ focused })}
        initialVisibleMonth={() => {
          if (this.state.date !== null) {
            return this.state.date;
          } else {
            return this.state.initialVisibleMonth;
          }
        }
        }
        isOutsideRange={(date) => {
          if (typeof this.state.minDateAllowed !== 'undefined' &&
              typeof this.state.maxDateAllowed !== 'undefined') {
            return date < this.state.minDateAllowed || date >= this.state.maxDateAllowed;
          } else if (typeof this.state.minDateAllowed === 'undefined' &&
                     typeof this.state.maxDateAllowed !== 'undefined') {
            return date >= this.state.maxDateAllowed;
          } else if (typeof this.state.minDateAllowed !== 'undefined' &&
                     typeof this.state.maxDateAllowed === 'undefined') {
            return date < this.state.minDateAllowed;
          } else {
            return false;
          }
        }
        }
        numberOfMonths={this.props.number_of_months_shown}
        withPortal={this.props.with_portal && verticalFlag}
        withFullScreenPortal={this.props.with_full_screen_portal && verticalFlag}
        firstDayOfWeek={this.props.first_day_of_week}
        enableOutSideDays={this.props.show_outside_days}
        monthFormat={this.props.month_format}
        displayFormat={this.props.display_format}
        placeholder={this.props.placeholder}
        showClearDate={this.props.clearable}
        disabled={this.props.disabled}
        keepOpenOnDateSelect={this.props.stay_open_on_select}
        reopenPickerOnClearDates={this.props.reopen_calendar_on_clear}
        isRTL={this.props.is_RTL}
        orientation={this.props.calendar_orientation}
        daySize={this.props.day_size}
      />
    );
  }
}

DatePickerSingle.propTypes = {
    id: PropTypes.string,

    /**
     * Specifies the starting date for the component, best practice is to pass
     * value via datetime object
     */
    date: PropTypes.string,

    /**
     * Specifies the lowest selectable date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    min_date_allowed: PropTypes.string,

    /**
     * Specifies the highest selectable date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    max_date_allowed: PropTypes.string,

    /**
     * Specifies the month that is initially presented when the user
     * opens the calendar. Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     *
     */
    initial_visible_month: PropTypes.string,

    /**
     * Size of rendered calendar days, higher number
     * means bigger day size and larger calendar overall
     */
    day_size: PropTypes.number,

    /**
     * Orientation of calendar, either vertical or horizontal.
     * Valid options are 'vertical' or 'horizontal'.
     */
    calendar_orientation: PropTypes.oneOf(['vertical', 'horizontal']),

    /**
     * Determines whether the calendar and days operate
     * from left to right or from right to left
     */
    is_RTL: PropTypes.bool,
    /**
     * Text that will be displayed in the input
     * box of the date picker when no date is selected.
     * Default value is 'Start Date'
     */
    placeholder: PropTypes.string,

    /**
     * If True, the calendar will automatically open when cleared
     */
    reopen_calendar_on_clear: PropTypes.bool,

    /**
    * Number of calendar months that are shown when calendar is opened
    */
    number_of_months_shown: PropTypes.number,

    /**
    * If True, calendar will open in a screen overlay portal,
    * not supported on vertical calendar
    */
    with_portal: PropTypes.bool,

    /**
    * If True, calendar will open in a full screen overlay portal, will
    * take precedent over 'withPortal' if both are set to True,
    * not supported on vertical calendar
    */
    with_full_screen_portal: PropTypes.bool,

    /**
     * Specifies what day is the first day of the week, values must be
     * from [0, ..., 6] with 0 denoting Sunday and 6 denoting Saturday
     */
    first_day_of_week: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6]),

    /**
    * If True the calendar will not close when the user has selected a value
    * and will wait until the user clicks off the calendar
    */
    stay_open_on_select: PropTypes.bool,

    /**
    * If True the calendar will display days that rollover into
    * the next month
    */
    show_outside_days: PropTypes.bool,

    /**
     * Specifies the format that the month will be displayed in the calendar,
     * valid formats are variations of "MM YY". For example:
     * "MM YY" renders as '05 97' for May 1997
     * "MMMM, YYYY" renders as 'May, 1997' for May 1997
     * "MMM, YY" renders as 'Sep, 97' for September 1997
     */
    month_format: PropTypes.string,

    /**
     * Specifies the format that the selected dates will be displayed
     * valid formats are variations of "MM YY DD". For example:
     * "MM YY DD" renders as '05 10 97' for May 10th 1997
     * "MMMM, YY" renders as 'May, 1997' for May 10th 1997
     * "M, D, YYYY" renders as '07, 10, 1997' for September 10th 1997
     * "MMMM" renders as 'May' for May 10 1997
     */
    display_format: PropTypes.string,

    /**
    * If True, no dates can be selected.
    */
    disabled: PropTypes.bool,

    /**
    * If True, there will be a button that allows for clearing the dates
    */
    clearable: PropTypes.bool,

    /**
    * Dash-assigned callback that gets fired when the value changes.
    */
    setProps: PropTypes.func,

    /**
    * Dash-assigned callback that gets fired when the value changes.
    */
    dashEvents: PropTypes.oneOf(['change'])
  };

DatePickerSingle.defaultProps = {
  calendar_orientation: 'horizontal',
  is_RTL: false,
  day_size: 39,
  with_portal: false,
  with_full_screen_portal: false,
  show_outside_days: true,
  first_day_of_week: 0,
  number_of_months_shown: 1,
  stay_open_on_select: false,
  reopen_calendar_on_clear: false,
  clearable: false,
  disabled: false
};
<|endoftext|>


<commit_before>import React from 'react';

import Banner from '../../Banner/Banner'
import Countdown from 'react-countdown';

const FALL_GM_2020_START_TIME = new Date('2020-10-05T18:30:00-07:00'); //October 5th 2020 at 6:30 PDT

export default function FallGM() {
	const calculateTimeStrings = ({days, hours, minutes, seconds}) => {
		let dayString = "Day";
		let hourString = "Hour";
		let minuteString = "Minute";
		let secondString = "Second";
		if(days !== 1){ dayString += "s" }
		if(hours !== 1){ hourString += "s" }
		if(minutes !== 1){ minuteString += "s" }
		if(seconds !== 1){ secondString += "s" }

		return {dayString, hourString, minuteString, secondString};
	}

	const renderCountdownOrNow = ({ days, hours, minutes, seconds, completed }) => {
		if (completed){ return <GMLiveProgram/> }
		else {
			const {dayString, hourString, minuteString, secondString} = calculateTimeStrings({days, hours, minutes, seconds});

			return (<GM_COUNTDOWN days={days} hours={hours} minutes={minutes} seconds={seconds}
			dayString={dayString} hourString={hourString} minuteString={minuteString} secondString={secondString}/>);
		}
	};

	return (

			<Banner decorative />
			<Countdown date={FALL_GM_2020_START_TIME} renderer={renderCountdownOrNow} />

	);
}

function GMLiveProgram() {
	function renderCommitteeSpeakers() {
		const committeeData = [
			{
				id: "studio",
				displayName: "ACM Studio",
				speakers: "Kade Adams, Evan Charfauros",
				logo: "/images/diamonds/studio_diamond.svg",
			},
			{
				id: "icpc",
				displayName: "ACM ICPC",
				speakers: "Nikil Selvam",
				logo: "/images/diamonds/icpc_diamond.svg",
			},
			{
				id: "design",
				displayName: "ACM Design",
				speakers: "Haki Alpay, Tomoki Fukazawa",
				logo: "/images/diamonds/design_diamond.svg",
			},
			{
				id: "cyber",
				displayName: "ACM Cyber",
				speakers: "Disha Zambani, Ellie Cheng",
				logo: "/images/diamonds/cyber_diamond.svg",
			},
			{
				id: "teach",
				displayName: "ACM Teach LA",
				speakers: "Matt Wang",
				logo: "/images/diamonds/teachla_diamond.svg",
			},
			{
				id: "w",
				displayName: "ACM W",
				speakers: "Julia Baylon",
				logo: "/images/diamonds/w_diamond.svg",
			},
			{
				id: "ai",
				displayName: "ACM AI",
				speakers: "John Dang",
				logo: "/images/diamonds/ai_diamond.svg",
			},
			{
				id: "hack",
				displayName: "ACM Hack",
				speakers: "Raji Jadhav",
				logo: "/images/diamonds/hack_diamond.svg",
			},
		]
		return committeeData.map((committee) => {
			const {id, logo, displayName, speakers} = committee;
			return (

						{displayName}

					: {speakers}

			);
		})
	}
	return (

Fall General Meeting 2020 is happening right now!

Join us from 6:30 to 7:10 p.m. PDT in Zoom

Follow along during the presentation with our slides

Join us from 7:10 p.m. PDT onwards in Discord

Chat with officers and learn what makes ACM special

Program

6:35 p.m. PDT

Rucha Patki: Welcome

6:38 p.m. PDT

Alumni: Why ACM?

Hear perspectives of alumni on what their experience with ACM at UCLA meant to them

Elena Escalas (class of 2020)

Kevin Tan (class of 2020)

6:40 p.m. PDT

Committee Presentations

Learn more about each of ACM's eight committees: what they have to offer, and why you should join them

							{renderCommitteeSpeakers()}

7:05 p.m. PDT

Lightning Round: Joining ACM / ACM Board

How to become even more involved with ACM beyond attending workshops and events

External: Katherine Chong

Internal: William Chern

Dev Team: Austin Keil, Nhi Van

Moonshots: Sharvani Jha, Megha Ilango

Internship Program: Gloria Yang, Sahen Rai

7:10 p.m. PDT

Tabling and Social

Join us in our official ACM Discord for Committee tabling and socials

Daniel Jaffe, Evan Zhong

	)
}

function GM_COUNTDOWN(props) {
	return (

Fall General Meeting 2020 begins in

{props.days}

{props.dayString}

{props.hours}

{props.hourString}

{props.minutes}

{props.minuteString}

{props.seconds}

{props.secondString}

Zoom link, slides, and other resources will be provided on Monday, October 5th, 2020 at 6:30 p.m. PDT

The Zoom portion of Fall General Meeting 2020 will be recorded and posted to our youtube channel

Can't wait to join in on the fun? Jump in right now with our Linktree or hop into the official Discord and say hello!

	);
}
<commit_msg>Emergency swap from GM_COUNTDOWN to GMLiveProgram (#77)<commit_after>import React from 'react';

import Banner from '../../Banner/Banner'
// import Countdown from 'react-countdown';

//const FALL_GM_2020_START_TIME = new Date('2020-10-05T18:30:00-07:00'); //October 5th 2020 at 6:30 PDT

export default function FallGM() {
	// const calculateTimeStrings = ({days, hours, minutes, seconds}) => {
	// 	let dayString = "Day";
	// 	let hourString = "Hour";
	// 	let minuteString = "Minute";
	// 	let secondString = "Second";
	// 	if(days !== 1){ dayString += "s" }
	// 	if(hours !== 1){ hourString += "s" }
	// 	if(minutes !== 1){ minuteString += "s" }
	// 	if(seconds !== 1){ secondString += "s" }

	// 	return {dayString, hourString, minuteString, secondString};
	// }

	// const renderCountdownOrNow = ({ days, hours, minutes, seconds, completed }) => {
	// 	if (completed){ return <GMLiveProgram/> }
	// 	else {
	// 		const {dayString, hourString, minuteString, secondString} = calculateTimeStrings({days, hours, minutes, seconds});

	// 		return (<GM_COUNTDOWN days={days} hours={hours} minutes={minutes} seconds={seconds}
	// 		dayString={dayString} hourString={hourString} minuteString={minuteString} secondString={secondString}/>);
	// 	}
	// };

	return (

			<Banner decorative />
			<GMLiveProgram/>

	);
}

function GMLiveProgram() {
	function renderCommitteeSpeakers() {
		const committeeData = [
			{
				id: "studio",
				displayName: "ACM Studio",
				speakers: "Kade Adams, Evan Charfauros",
				logo: "/images/diamonds/studio_diamond.svg",
			},
			{
				id: "icpc",
				displayName: "ACM ICPC",
				speakers: "Nikil Selvam",
				logo: "/images/diamonds/icpc_diamond.svg",
			},
			{
				id: "design",
				displayName: "ACM Design",
				speakers: "Haki Alpay, Tomoki Fukazawa",
				logo: "/images/diamonds/design_diamond.svg",
			},
			{
				id: "cyber",
				displayName: "ACM Cyber",
				speakers: "Disha Zambani, Ellie Cheng",
				logo: "/images/diamonds/cyber_diamond.svg",
			},
			{
				id: "teach",
				displayName: "ACM Teach LA",
				speakers: "Matt Wang",
				logo: "/images/diamonds/teachla_diamond.svg",
			},
			{
				id: "w",
				displayName: "ACM W",
				speakers: "Julia Baylon",
				logo: "/images/diamonds/w_diamond.svg",
			},
			{
				id: "ai",
				displayName: "ACM AI",
				speakers: "John Dang",
				logo: "/images/diamonds/ai_diamond.svg",
			},
			{
				id: "hack",
				displayName: "ACM Hack",
				speakers: "Raji Jadhav",
				logo: "/images/diamonds/hack_diamond.svg",
			},
		]
		return committeeData.map((committee) => {
			const {id, logo, displayName, speakers} = committee;
			return (

						{displayName}

					: {speakers}

			);
		})
	}
	return (

Fall General Meeting 2020 is happening right now!

Join us from 6:30 to 7:10 p.m. PDT in Zoom

Follow along during the presentation with our slides

Join us from 7:10 p.m. PDT onwards in Discord

Chat with officers and learn what makes ACM special

Program

6:35 p.m. PDT

Rucha Patki: Welcome

6:38 p.m. PDT

Alumni: Why ACM?

Hear perspectives of alumni on what their experience with ACM at UCLA meant to them

Elena Escalas (class of 2020)

Kevin Tan (class of 2020)

6:40 p.m. PDT

Committee Presentations

Learn more about each of ACM's eight committees: what they have to offer, and why you should join them

							{renderCommitteeSpeakers()}

7:05 p.m. PDT

Lightning Round: Joining ACM / ACM Board

How to become even more involved with ACM beyond attending workshops and events

External: Katherine Chong

Internal: William Chern

Dev Team: Austin Keil, Nhi Van

Moonshots: Sharvani Jha, Megha Ilango

Internship Program: Gloria Yang, Sahen Rai

7:10 p.m. PDT

Tabling and Social

Join us in our official ACM Discord for Committee tabling and socials

Daniel Jaffe, Evan Zhong

	)
}

// function GM_COUNTDOWN(props) {
// 	return (
//

//
Fall General Meeting 2020 begins in

//

//

//

//
{props.days}

//
{props.dayString}

//

//

//

//
{props.hours}

//
{props.hourString}

//

//

//

//
{props.minutes}

//
{props.minuteString}

//

//

//

//
{props.seconds}

//
{props.secondString}

//

//

//
Zoom link, slides, and other resources will be provided on Monday, October 5th, 2020 at 6:30 p.m. PDT

//
The Zoom portion of Fall General Meeting 2020 will be recorded and posted to our youtube channel

//
Can't wait to join in on the fun? Jump in right now with our Linktree or hop into the official Discord and say hello!

//
//

// 	);
// }
<|endoftext|>


<commit_before>import React from "react";
import XYGraph from "../XYGraph";
import { connect } from "react-redux";
import { nest } from "../../../utils/helpers"

import {
    axisBottom,
    axisLeft,
    extent,
    format,
    line,
    scaleLinear,
    scaleTime,
    select,
    brushX,
    voronoi,
    merge,
    event
} from "d3";

import {properties} from "./default.config";

class LineGraph extends XYGraph {

    yKey   = 'columnType'
    yValue = 'yColumn'

    constructor(props) {
        super(props, properties);
        this.brush = brushX();
    }

    render() {

        const {
            data,
            width,
            height
        } = this.props;

        if (!data || !data.length)
            return;

        const {
          chartHeightToPixel,
          chartWidthToPixel,
          circleToPixel,
          colors,
          dateHistogram,
          legend,
          linesColumn: configLinesColumn,
          margin,
          stroke,
          xColumn,
          xLabel,
          xTickFormat,
          xTickGrid,
          xTicks,
          xTickSizeInner,
          xTickSizeOuter,
          yColumn,
          yTickFormat,
          yTickGrid,
          yTicks,
          yTickSizeInner,
          yTickSizeOuter,
          brushEnabled,
          zeroStart,
          circleRadius,
          defaultY,
          defaultYColor,
          showNull
        } = this.getConfiguredProperties();

        let finalYColumn = typeof yColumn === 'object' ? yColumn : [yColumn];

        let updatedLinesLabel = [];

        let linesColumn = configLinesColumn || [yColumn]

        if(linesColumn) {
            updatedLinesLabel = typeof linesColumn === 'object' ? linesColumn : [linesColumn];
        }

        let legendsData = updatedLinesLabel.map((d, i) => {
            return {
                'key'   : d,
                'value' : finalYColumn[i] ? finalYColumn[i] : d
            }
        })

        let flatData = []
        data.forEach((d) => {
            if(!dateHistogram || d[xColumn] <= Date.now()) {
                legendsData.forEach((ld) => {

                    let key = typeof linesColumn === 'object' ? ld['key'] : d[ld['key']]
                    if(typeof key === "object" || key === "") {
                        return
                    }

                    flatData.push(Object.assign({
                        [this.yValue]: d[ld['value']],
                        [this.yKey]: key
                    }, d));
                });
            }
        });

        // Nesting data on the basis of yAxis
        let nestLinesData = nest({
            data: flatData,
            key: this.yKey
        })

        // Nesting data on the basis of xAxis (timestamp)
        let nestedXData = nest({
            data: flatData,
            key: xColumn,
            sortColumn: xColumn
        })

        let linesData = []
        // Check x column data, if not found either skip or set to 0 (if showNull is true)
        nestLinesData.forEach(item => {

            let d = Object.assign({}, item)
            let counter = 0
            let elem = null

            elem = {
                key: `${item.key}${counter}`,
                values: []
            }
            // Inserting new object if data not found
            nestedXData.forEach(list => {
                let index = (d.values).findIndex(o => {
                   return `${o[xColumn]}` === `${list.key}`
                })

                if(index !== -1
                    && d.values[index][this.yValue] !== ""
                    && typeof d.values[index][this.yValue] !== 'undefined'
                    && typeof d.values[index][this.yValue] !== 'object'
                ) {
                    elem.values.push(d.values[index])
                } else {
                    // If showNull is true, insert new object with yValue=0
                    if(showNull !== false) {
                        elem.values.push({
                            [this.yValue]: 0,
                            [this.yKey]: d.key,
                            [xColumn]: list.key
                        })
                    } else {
                        // Make new truncated line if yValue is null
                        if(elem.values.length) {
                            linesData.push(elem)
                            counter++;
                            elem = {
                                key: `${item.key}${counter}`,
                                values: []
                            }
                        }
                    }
                }
            })

            if(elem.values.length) {
                linesData.push(elem)
            }
        })

        if(!linesData.length) {
            return (

                    No data to display

            )
        }

        let filterDatas = merge(linesData.map(function(d) { return d.values; }))

        const isVerticalLegend = legend.orientation === 'vertical';
        const xLabelFn         = (d) => d[xColumn];
        const yLabelFn         = (d) => parseFloat(d[this.yValue]);
        const legendFn         = (d) => d[this.yKey];
        const label            = (d) => d[this.yKey];

        const scale            = this.scaleColor(filterDatas, this.yKey);
        const getColor         = (d) => scale ? scale(d[this.yKey] || d["key"]) : stroke.color || colors[0];

        let xAxisHeight       = xLabel ? chartHeightToPixel : 0;
        let legendWidth       = legend.show ? this.longestLabelLength(filterDatas, legendFn) * chartWidthToPixel : 0;

        let yLabelWidth       = this.longestLabelLength(filterDatas, yLabelFn, yTickFormat) * chartWidthToPixel;
        let leftMargin        = margin.left + yLabelWidth;
        let availableWidth    = width - (margin.left + margin.right + yLabelWidth);
        let availableHeight   = height - (margin.top + margin.bottom + chartHeightToPixel + xAxisHeight);

        if (legend.show)
        {
            legend.width = legendWidth || 1;

            // Compute the available space considering a legend
            if (isVerticalLegend)
            {
                leftMargin      +=  legend.width;
                availableWidth  -=  legend.width;
            }
            else {
                const nbElementsPerLine  = parseInt(availableWidth / legend.width, 10);
                const nbLines            = parseInt(linesData.length / nbElementsPerLine, 10);
                availableHeight         -= nbLines * legend.circleSize * circleToPixel + chartHeightToPixel;
            }
        }

        let yExtent = this.updateYExtent(extent(filterDatas, yLabelFn), zeroStart);

        let xScale;

        if (dateHistogram) {
            xScale = scaleTime()
              .domain(extent(filterDatas, xLabelFn));
        } else {
            xScale = scaleLinear()
              .domain(extent(filterDatas, xLabelFn));
        }

        const yScale = scaleLinear()
          .domain(yExtent);

        xScale.range([0, availableWidth]);
        yScale.range([availableHeight, 0]);

        // calculate new range from defaultY
        let horizontalLine,
        defaultYvalue,
        horizontalLineData

        if(defaultY) {

            defaultYvalue = defaultY
            let [startRange, endRange] = yScale.domain()

            if(typeof defaultY === 'object' && defaultY.column) {
                const dataSource = defaultY.source || 'data'
                horizontalLineData = this.props[dataSource] ? this.props[dataSource][0] : {}
                defaultYvalue = horizontalLineData[defaultY.column] || null
            }

            startRange = startRange > defaultYvalue ? defaultYvalue - 1 : startRange
            endRange = endRange < defaultYvalue ? defaultYvalue + 1 : endRange
            yScale.domain([startRange, endRange]);

        }

        const xAxis = axisBottom(xScale)
          .tickSizeInner(xTickGrid ? -availableHeight : xTickSizeInner)
          .tickSizeOuter(xTickSizeOuter);

        if(xTickFormat){
            xAxis.tickFormat(format(xTickFormat));
        }

        if(xTicks){
            xAxis.ticks(xTicks);
        }

        const yAxis = axisLeft(yScale)
          .tickSizeInner(yTickGrid ? -availableWidth : yTickSizeInner)
          .tickSizeOuter(yTickSizeOuter);

        if(yTickFormat){
            yAxis.tickFormat(format(yTickFormat));
        }

        if(yTicks){
            yAxis.ticks(yTicks);
        }

        const lineGenerator = line()
          .x( d => xScale(d[xColumn]))
          .y( d => yScale(d[this.yValue]))

        let xTitlePosition = {
            left: leftMargin + availableWidth / 2,
            top: margin.top + availableHeight + chartHeightToPixel + xAxisHeight
        }
        let yTitlePosition = {
            // We use chartWidthToPixel to compensate the rotation of the title
            left: margin.left + chartWidthToPixel + (isVerticalLegend ? legend.width : 0),
            top: margin.top + availableHeight / 2
        }

        if(brushEnabled){
            this.brush
                .extent([[0, 0], [availableWidth, availableHeight]])
                .on("end", () => {
                    // If there is a brushed region...
                    if(event.selection){
                        const [startTime, endTime] = event.selection
                          .map(xScale.invert, xScale) // Convert from pixel coords to Date objects.
                          .map((date) => date.getTime()); // Convert from Date to epoch milliseconds.
                        const queryParams = Object.assign({}, this.props.context, { startTime, endTime });
                        this.props.goTo(window.location.pathname, queryParams);
                    }
                });
        }

        const tooltipOverlay = voronoi()
            .x( d => xScale(d[xColumn]))
            .y( d => yScale(d[this.yValue]))
            .extent([[-leftMargin, -margin.top], [width + margin.right, height + margin.bottom]])
            .polygons(filterDatas);

        const tooltipOffset = (d) => JSON.stringify({
          'bottom': margin.top + yScale(d[this.yValue]),
          'right': xScale(d[xColumn]) + leftMargin
        });

        //draw horizontal line
        if(defaultYvalue) {
            let y = yScale(defaultYvalue),
            height = 20,
            tooltip = []

            if(horizontalLineData && defaultY.tooltip) {
                tooltip = this.tooltipProps(Object.assign({}, horizontalLineData ,{tooltipName: 'defaultY'}))
            }

            horizontalLine = (
                <g>
                    <rect
                        height={height}
                        width={availableWidth}
                        x="0"
                        y={ y - height/2}
                        opacity="0"
                        { ...tooltip }
                        data-offset="{ 'left' : 0, 'bottom' : 0}"
                    />
                    <line
                        x1="0"
                        y1={y}
                        x2={availableWidth}
                        y2={y}
                        stroke={ defaultYColor ? defaultYColor : "rgb(255,0,0)"}
                        strokeWidth="1.5"
                        opacity="0.7"
                        className="horizontalLine"
                    />
                </g>
            )
        }

        return (

                {this.tooltip}
                <svg width={width} height={height}>
                    {this.axisTitles(xTitlePosition, yTitlePosition)}
                    <g transform={ `translate(${leftMargin},${margin.top})` } >
                        <g
                            key="xAxis"
                            ref={ (el) => select(el).call(xAxis) }
                            transform={ `translate(0,${availableHeight})` }
                        />
                        <g
                            key="yAxis"
                            ref={ (el) => select(el).call(yAxis) }
                        />
                        <g>
                          {linesData.map((d, i) =>
                              (d.values.length === 1) ?
                                  <circle key={d.key} cx={xScale(d.values[0][xColumn])} cy={yScale(d.values[0][this.yValue])} r={circleRadius} fill={getColor(d.values[0])} />
                              :
                              <path
                                  key={ d.key }
                                  fill="none"
                                  stroke={ getColor(d.values[0] || d) }
                                  strokeWidth={ stroke.width }
                                  d={ lineGenerator(d.values) }
                              />
                          )}
                        </g>
                        <g>
                          {tooltipOverlay.map((d, i) =>
                              <g
                                  key={ i }
                                  { ...this.tooltipProps(d.data) }
                                  data-offset={tooltipOffset(d.data)}
                                  data-effect="solid"
                              >

                                  /*
                                    This rectangle is a hack
                                    to position tooltips correctly.
                                    Due to this rectangle, the boundingClientRect
                                    used by ReactTooltip for positioning the tooltips
                                    has an upper left corner at (0, 0).
                                  */
                                  <rect
                                      x={-leftMargin}
                                      y={-margin.top}
                                      width="1"
                                      height="1"
                                      fill="none"
                                  />

                                  <path
                                      key={ i }
                                      fill="none"
                                      d={ d == null ? null : "M" + d.join("L") + "Z" }
                                      style={{"pointerEvents": "all"}}
                                  />
                              </g>
                          )}
                        </g>
                        { horizontalLine }
                        {
                            brushEnabled &&
                            <g
                                key="brush"
                                ref={ (el) => select(el).call(this.brush) }
                            />
                        }
                    </g>
                    {this.renderLegend(filterDatas, legend, getColor, label)}
                </svg>

        );
    }
}
LineGraph.propTypes = {
  configuration: React.PropTypes.object,
  response: React.PropTypes.object
};

const actionCreators = (dispatch) => ({
});

export default connect(null, actionCreators)(LineGraph);
<commit_msg>Add horizontal line at 0 y axis<commit_after>import React from "react";
import XYGraph from "../XYGraph";
import { connect } from "react-redux";
import { nest } from "../../../utils/helpers"

import {
    axisBottom,
    axisLeft,
    extent,
    format,
    line,
    scaleLinear,
    scaleTime,
    select,
    brushX,
    voronoi,
    merge,
    event
} from "d3";

import {properties} from "./default.config";

class LineGraph extends XYGraph {

    yKey   = 'columnType'
    yValue = 'yColumn'

    constructor(props) {
        super(props, properties);
        this.brush = brushX();
    }

    render() {

        const {
            data,
            width,
            height
        } = this.props;

        if (!data || !data.length)
            return;

        const {
          chartHeightToPixel,
          chartWidthToPixel,
          circleToPixel,
          colors,
          dateHistogram,
          legend,
          linesColumn: configLinesColumn,
          margin,
          stroke,
          xColumn,
          xLabel,
          xTickFormat,
          xTickGrid,
          xTicks,
          xTickSizeInner,
          xTickSizeOuter,
          yColumn,
          yTickFormat,
          yTickGrid,
          yTicks,
          yTickSizeInner,
          yTickSizeOuter,
          brushEnabled,
          zeroStart,
          circleRadius,
          defaultY,
          defaultYColor,
          showNull
        } = this.getConfiguredProperties();

        let finalYColumn = typeof yColumn === 'object' ? yColumn : [yColumn];

        let updatedLinesLabel = [];

        let linesColumn = configLinesColumn || [yColumn]

        if(linesColumn) {
            updatedLinesLabel = typeof linesColumn === 'object' ? linesColumn : [linesColumn];
        }

        let legendsData = updatedLinesLabel.map((d, i) => {
            return {
                'key'   : d,
                'value' : finalYColumn[i] ? finalYColumn[i] : d
            }
        })

        let flatData = []
        data.forEach((d) => {
            if(!dateHistogram || d[xColumn] <= Date.now()) {
                legendsData.forEach((ld) => {

                    let key = typeof linesColumn === 'object' ? ld['key'] : d[ld['key']]
                    if(typeof key === "object" || key === "") {
                        return
                    }

                    flatData.push(Object.assign({
                        [this.yValue]: d[ld['value']],
                        [this.yKey]: key
                    }, d));
                });
            }
        });

        // Nesting data on the basis of yAxis
        let nestLinesData = nest({
            data: flatData,
            key: this.yKey
        })

        // Nesting data on the basis of xAxis (timestamp)
        let nestedXData = nest({
            data: flatData,
            key: xColumn,
            sortColumn: xColumn
        })

        let linesData = []
        // Check x column data, if not found either skip or set to 0 (if showNull is true)
        nestLinesData.forEach(item => {

            let d = Object.assign({}, item)
            let counter = 0
            let elem = null

            elem = {
                key: `${item.key}${counter}`,
                values: []
            }
            // Inserting new object if data not found
            nestedXData.forEach(list => {
                let index = (d.values).findIndex(o => {
                   return `${o[xColumn]}` === `${list.key}`
                })

                if(index !== -1
                    && d.values[index][this.yValue] !== ""
                    && typeof d.values[index][this.yValue] !== 'undefined'
                    && typeof d.values[index][this.yValue] !== 'object'
                ) {
                    elem.values.push(d.values[index])
                } else {
                    // If showNull is true, insert new object with yValue=0
                    if(showNull !== false) {
                        elem.values.push({
                            [this.yValue]: 0,
                            [this.yKey]: d.key,
                            [xColumn]: list.key
                        })
                    } else {
                        // Make new truncated line if yValue is null
                        if(elem.values.length) {
                            linesData.push(elem)
                            counter++;
                            elem = {
                                key: `${item.key}${counter}`,
                                values: []
                            }
                        }
                    }
                }
            })

            if(elem.values.length) {
                linesData.push(elem)
            }
        })

        if(!linesData.length) {
            return (

                    No data to display

            )
        }

        let filterDatas = merge(linesData.map(function(d) { return d.values; }))

        const isVerticalLegend = legend.orientation === 'vertical';
        const xLabelFn         = (d) => d[xColumn];
        const yLabelFn         = (d) => parseFloat(d[this.yValue]);
        const legendFn         = (d) => d[this.yKey];
        const label            = (d) => d[this.yKey];

        const scale            = this.scaleColor(filterDatas, this.yKey);
        const getColor         = (d) => scale ? scale(d[this.yKey] || d["key"]) : stroke.color || colors[0];

        let xAxisHeight       = xLabel ? chartHeightToPixel : 0;
        let legendWidth       = legend.show ? this.longestLabelLength(filterDatas, legendFn) * chartWidthToPixel : 0;

        let yLabelWidth       = this.longestLabelLength(filterDatas, yLabelFn, yTickFormat) * chartWidthToPixel;
        let leftMargin        = margin.left + yLabelWidth;
        let availableWidth    = width - (margin.left + margin.right + yLabelWidth);
        let availableHeight   = height - (margin.top + margin.bottom + chartHeightToPixel + xAxisHeight);

        if (legend.show)
        {
            legend.width = legendWidth || 1;

            // Compute the available space considering a legend
            if (isVerticalLegend)
            {
                leftMargin      +=  legend.width;
                availableWidth  -=  legend.width;
            }
            else {
                const nbElementsPerLine  = parseInt(availableWidth / legend.width, 10);
                const nbLines            = parseInt(linesData.length / nbElementsPerLine, 10);
                availableHeight         -= nbLines * legend.circleSize * circleToPixel + chartHeightToPixel;
            }
        }

        let range = extent(filterDatas, yLabelFn)

        let yExtent = this.updateYExtent(range, zeroStart);

        let xScale;

        if (dateHistogram) {
            xScale = scaleTime()
              .domain(extent(filterDatas, xLabelFn));
        } else {
            xScale = scaleLinear()
              .domain(extent(filterDatas, xLabelFn));
        }

        const yScale = scaleLinear()
          .domain(yExtent);

        xScale.range([0, availableWidth]);
        yScale.range([availableHeight, 0]);

        // calculate new range from defaultY
        let horizontalLine,
        defaultYvalue,
        horizontalLineData

        if(defaultY) {

            defaultYvalue = defaultY
            let [startRange, endRange] = yScale.domain()

            if(typeof defaultY === 'object' && defaultY.column) {
                const dataSource = defaultY.source || 'data'
                horizontalLineData = this.props[dataSource] ? this.props[dataSource][0] : {}
                defaultYvalue = horizontalLineData[defaultY.column] || null
            }

            startRange = startRange > defaultYvalue ? defaultYvalue - 1 : startRange
            endRange = endRange < defaultYvalue ? defaultYvalue + 1 : endRange
            yScale.domain([startRange, endRange]);

        }

        const xAxis = axisBottom(xScale)
          .tickSizeInner(xTickGrid ? -availableHeight : xTickSizeInner)
          .tickSizeOuter(xTickSizeOuter);

        if(xTickFormat){
            xAxis.tickFormat(format(xTickFormat));
        }

        if(xTicks){
            xAxis.ticks(xTicks);
        }

        const yAxis = axisLeft(yScale)
          .tickSizeInner(yTickGrid ? -availableWidth : yTickSizeInner)
          .tickSizeOuter(yTickSizeOuter);

        if(yTickFormat){
            yAxis.tickFormat(format(yTickFormat));
        }

        if(yTicks){
            yAxis.ticks(yTicks);
        }

        const lineGenerator = line()
          .x( d => xScale(d[xColumn]))
          .y( d => yScale(d[this.yValue]))

        let xTitlePosition = {
            left: leftMargin + availableWidth / 2,
            top: margin.top + availableHeight + chartHeightToPixel + xAxisHeight
        }
        let yTitlePosition = {
            // We use chartWidthToPixel to compensate the rotation of the title
            left: margin.left + chartWidthToPixel + (isVerticalLegend ? legend.width : 0),
            top: margin.top + availableHeight / 2
        }

        if(brushEnabled){
            this.brush
                .extent([[0, 0], [availableWidth, availableHeight]])
                .on("end", () => {
                    // If there is a brushed region...
                    if(event.selection){
                        const [startTime, endTime] = event.selection
                          .map(xScale.invert, xScale) // Convert from pixel coords to Date objects.
                          .map((date) => date.getTime()); // Convert from Date to epoch milliseconds.
                        const queryParams = Object.assign({}, this.props.context, { startTime, endTime });
                        this.props.goTo(window.location.pathname, queryParams);
                    }
                });
        }

        const tooltipOverlay = voronoi()
            .x( d => xScale(d[xColumn]))
            .y( d => yScale(d[this.yValue]))
            .extent([[-leftMargin, -margin.top], [width + margin.right, height + margin.bottom]])
            .polygons(filterDatas);

        const tooltipOffset = (d) => JSON.stringify({
          'bottom': margin.top + yScale(d[this.yValue]),
          'right': xScale(d[xColumn]) + leftMargin
        });

        //draw horizontal line
        if(defaultYvalue) {
            let y = yScale(defaultYvalue),
            height = 20,
            tooltip = []

            if(horizontalLineData && defaultY.tooltip) {
                tooltip = this.tooltipProps(Object.assign({}, horizontalLineData ,{tooltipName: 'defaultY'}))
            }

            horizontalLine = (
                <g>
                    <rect
                        height={height}
                        width={availableWidth}
                        x="0"
                        y={ y - height/2}
                        opacity="0"
                        { ...tooltip }
                        data-offset="{ 'left' : 0, 'bottom' : 0}"
                    />
                    <line
                        x1="0"
                        y1={y}
                        x2={availableWidth}
                        y2={y}
                        stroke={ defaultYColor ? defaultYColor : "rgb(255,0,0)"}
                        strokeWidth="1.5"
                        opacity="0.7"
                        className="horizontalLine"
                    />
                </g>
            )
        }

        let defaultLine =
            range[0] < 0 && range[1] > 0 ?
                <line
                    x1="0"
                    y1={yScale(0)}
                    x2={availableWidth}
                    y2={yScale(0)}
                    stroke={ "rgb(0,0,0)"}
                    opacity="0.3"
                />
                : ''

        return (

                {this.tooltip}
                <svg width={width} height={height}>
                    {this.axisTitles(xTitlePosition, yTitlePosition)}
                    <g transform={ `translate(${leftMargin},${margin.top})` } >
                        <g
                            key="xAxis"
                            ref={ (el) => select(el).call(xAxis) }
                            transform={ `translate(0,${availableHeight})` }
                        />
                        <g
                            key="yAxis"
                            ref={ (el) => select(el).call(yAxis) }
                        />

                        <g>
                          {linesData.map((d, i) =>
                              (d.values.length === 1) ?
                                  <circle key={d.key} cx={xScale(d.values[0][xColumn])} cy={yScale(d.values[0][this.yValue])} r={circleRadius} fill={getColor(d.values[0])} />
                              :
                              <path
                                  key={ d.key }
                                  fill="none"
                                  stroke={ getColor(d.values[0] || d) }
                                  strokeWidth={ stroke.width }
                                  d={ lineGenerator(d.values) }
                              />
                          )}
                        </g>
                        <g>
                          {tooltipOverlay.map((d, i) =>
                              <g
                                  key={ i }
                                  { ...this.tooltipProps(d.data) }
                                  data-offset={tooltipOffset(d.data)}
                                  data-effect="solid"
                              >

                                  /*
                                    This rectangle is a hack
                                    to position tooltips correctly.
                                    Due to this rectangle, the boundingClientRect
                                    used by ReactTooltip for positioning the tooltips
                                    has an upper left corner at (0, 0).
                                  */
                                  <rect
                                      x={-leftMargin}
                                      y={-margin.top}
                                      width="1"
                                      height="1"
                                      fill="none"
                                  />

                                  <path
                                      key={ i }
                                      fill="none"
                                      d={ d == null ? null : "M" + d.join("L") + "Z" }
                                      style={{"pointerEvents": "all"}}
                                  />
                              </g>
                          )}
                        </g>
                        { defaultLine }
                        { horizontalLine }
                        {
                            brushEnabled &&
                            <g
                                key="brush"
                                ref={ (el) => select(el).call(this.brush) }
                            />
                        }
                    </g>
                    {this.renderLegend(filterDatas, legend, getColor, label)}
                </svg>

        );
    }
}
LineGraph.propTypes = {
  configuration: React.PropTypes.object,
  response: React.PropTypes.object
};

const actionCreators = (dispatch) => ({
});

export default connect(null, actionCreators)(LineGraph);
<|endoftext|>


<commit_before>import assert from 'assert';

describe('Test Login', () => {

	it('Local log-in works', () => {

		browser
      .url('http://localhost:3000/login');

    browser.waitForExist('#input-email');

    browser
      .setValue('#input-email', 'vhalme@gmail.com')
      .setValue('#input-password', 'test1')
      .click('form button');

    browser.waitForExist('#logout-link');

  });

});
<commit_msg>Fixed e2e test<commit_after>import assert from 'assert';

describe('Test Login', () => {

	it('Local log-in works', () => {

		browser
      .url('http://localhost:3000/login');

    browser.waitForExist('#login-email');

    browser
      .setValue('#input-email', 'vhalme@gmail.com')
      .setValue('#input-password', 'test1')
      .click('form button');

    browser.waitForExist('#logout-link');

  });

});
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react'
import Grid from 'react-bootstrap/lib/Grid'
import Row from 'react-bootstrap/lib/Row'
import Col from 'react-bootstrap/lib/Col'

export default class RegistrationForm extends Component {
  static propTypes = {
    formUpdate: PropTypes.func
  }

  constructor(props) {
    super(props);
    this.state = props.state || {}
  }

  render() {
    const { state } = this.props
    return (
      <Grid>
      <Row>
        <Col md={12} id='title'>Verify Registration Information</Col>
      </Row>
      <Row>
        <Col md={6}>Name</Col>
        <Col md={6} id='userName'>{state.userName}</Col>
      </Row>
      <Row>
        <Col md={6}>Email</Col>
        <Col md={6} id='email'>{state.email}</Col>
      </Row>
      </Grid>
    )
  }

}
<commit_msg>Adds component for User Registration Form verification<commit_after>import React, {Component, PropTypes} from 'react'
import Grid from 'react-bootstrap/lib/Grid'
import Row from 'react-bootstrap/lib/Row'
import Col from 'react-bootstrap/lib/Col'

export default class RegistrationForm extends Component {
  static propTypes = {
    formUpdate: PropTypes.func
  }

  constructor(props) {
    super(props);
    this.state = props.state || {}
  }

  render() {
    const { state } = this.props
    return (
      <Grid>
      <Row>
        <Col md={12} id='title'>Verify Registration Information</Col>
      </Row>
      <Row>
        <Col md={6}>Name</Col>
        <Col md={6} id='userName'>{state.userName}</Col>
      </Row>
      <Row>
        <Col md={6}>Email</Col>
        <Col md={6} id='email'>{state.email}</Col>
      </Row>
      <Row>
        <Col md={12}>
          <button id='edit'>Edit</button>
          <button id='confirm'>Confirm</button>
        </Col>
      </Row>
      </Grid>
    )
  }

}
<|endoftext|>


<commit_before>function colorThemeToStyles ({
  primaryMain,
  primaryMainLight,
  complementaryMain,
  complementaryMainLight,
  primarySecondary,
  primarySecondaryLight,
  complementarySecondary,
  complementarySecondaryLight
}) {
  const success = {
    color: primarySecondary,
    colorPressed: primarySecondaryLight
  }
  const action = {
    color: primaryMain,
    colorPressed: primaryMainLight
  }
  const error = {
    color: complementaryMain,
    colorPressed: complementaryMainLight
  }
  const warning = {
    color: complementarySecondary,
    colorPressed: complementarySecondaryLight
  }

  return {
    variants: {
      success,
      action,
      error,
      warning
    }
  }
}

export default function create ({colorThemeState_}) {
  const styles_ = colorThemeState_
  .map(colorThemeToStyles)

  return styles_
}
<commit_msg>add colors update<commit_after>function colorThemeToStyles ({
  primaryMain,
  primaryMainLight,
  complementaryMain,
  complementaryMainLight,
  primarySecondary,
  primarySecondaryLight,
  complementarySecondary,
  complementarySecondaryLight
}) {
  const success = {
    color: primarySecondary,
    colorPressed: primarySecondaryLight
  }
  const action = {
    color: primaryMain,
    colorPressed: primaryMainLight
  }
  const error = {
    color: complementarySecondary,
    colorPressed: complementarySecondaryLight
  }
  const warning = {
    color: complementaryMain,
    colorPressed: complementaryMainLight
  }

  return {
    variants: {
      success,
      action,
      error,
      warning
    }
  }
}

export default function create ({colorThemeState_}) {
  const styles_ = colorThemeState_
  .map(colorThemeToStyles)

  return styles_
}
<|endoftext|>