"use strict";
/* Copyright 2015 Bloomberg Finance L.P.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OHLCModel = void 0;
const d3 = __importStar(require("d3"));
const _ = __importStar(require("underscore"));
const MarkModel_1 = require("./MarkModel");
const serialize = __importStar(require("./serialize"));
class OHLCModel extends MarkModel_1.MarkModel {
    defaults() {
        return Object.assign(Object.assign({}, MarkModel_1.MarkModel.prototype.defaults()), { _model_name: 'OHLCModel', _view_name: 'OHLC', x: [], y: [], scales_metadata: {
                x: { orientation: 'horizontal', dimension: 'x' },
                y: { orientation: 'vertical', dimension: 'y' },
            }, marker: 'candle', stroke: null, stroke_width: 1.0, colors: ['green', 'red'], opacities: [], format: 'ohlc' });
    }
    initialize(attributes, options) {
        super.initialize(attributes, options);
        this.on_some_change(['x', 'y'], this.update_data, this);
        this.on_some_change(['preserve_domain'], this.update_domains, this);
        this.on('change:format', this.updateFormat, this);
        this.px = { o: -1, h: -1, l: -1, c: -1 };
        this.mark_data = [];
        this.update_data();
        this.update_domains();
        this.updateFormat();
    }
    updateFormat() {
        this.update_data();
        this.trigger('format_updated');
    }
    update_data() {
        let x = this.get('x');
        let y = this.get('y');
        const format = this.get('format');
        // Local private function to report errors in format
        function printBadFormat(format) {
            if (console) {
                console.error("Invalid OHLC format: '" + format + "'");
            }
        }
        // Generate positional map and check for duplicate characters
        this.px = format
            .toLowerCase()
            .split('')
            .reduce((dict, key, val) => {
            if (dict[key] !== -1) {
                printBadFormat(format);
                x = [];
                y = [];
            }
            dict[key] = val;
            return dict;
        }, { o: -1, h: -1, l: -1, c: -1 });
        // We cannot have high without low and vice versa
        if ((this.px.h !== -1 && this.px.l === -1) ||
            (this.px.h === -1 && this.px.l !== -1) ||
            format.length < 2 ||
            format.length > 4) {
            printBadFormat(format);
            x = [];
            y = [];
        }
        else {
            // Verify that OHLC data is valid
            const px = this.px;
            if ((this.px.h !== -1 &&
                !y.every((d) => {
                    return d[px.h] === d3.max(d) && d[px.l] === d3.min(d);
                })) ||
                !y.every((d) => {
                    return d.length === format.length;
                })) {
                x = [];
                y = [];
                if (console) {
                    console.error('Invalid OHLC data');
                }
            }
        }
        // Make x and y data the same length
        if (x.length > y.length) {
            x = x.slice(0, y.length);
        }
        else if (x.length < y.length) {
            y = y.slice(0, x.length);
        }
        this.mark_data = _.zip(x, y);
        this.update_domains();
        this.trigger('data_updated');
    }
    update_domains() {
        if (!this.mark_data) {
            return;
        }
        const scales = this.get('scales');
        const xScale = scales.x, yScale = scales.y;
        let min_x_dist = Number.POSITIVE_INFINITY;
        let max_y_height = 0;
        let dist = 0;
        let height = 0;
        /*
         * Compute the minimum x distance between the data points. We will
         * use this to pad either side of the x domain.
         * Also compute the maximum height of all of the marks (i.e. maximum
         * distance from high to low) and use that to pad the y domain.
         */
        for (let i = 0; i < this.mark_data.length; i++) {
            if (i > 0) {
                dist = this.mark_data[i][0] - this.mark_data[i - 1][0];
                if (dist < min_x_dist) {
                    min_x_dist = dist;
                }
            }
            height = this.mark_data[i][this.px.h] - this.mark_data[i][this.px.l];
            if (height > max_y_height) {
                max_y_height = height;
            }
        }
        if (this.mark_data.length < 2) {
            min_x_dist = 0;
        }
        let min;
        let max;
        // X Scale
        if (!this.get('preserve_domain').x && this.mark_data.length !== 0) {
            if (xScale.type === 'ordinal') {
                xScale.compute_and_set_domain(this.mark_data.map((d) => {
                    return d[0];
                }));
            }
            else {
                min = d3.min(this.mark_data.map((d) => {
                    return d[0];
                }));
                max = d3.max(this.mark_data.map((d) => {
                    return d[0];
                }));
                if (max instanceof Date) {
                    max = max.getTime();
                }
                xScale.set_domain([min - min_x_dist / 2, max + min_x_dist / 2], this.model_id + '_x');
            }
        }
        else {
            xScale.del_domain([], this.model_id + '_x');
        }
        // Y Scale
        if (!this.get('preserve_domain').y && this.mark_data.length !== 0) {
            // Remember that elem contains OHLC data here so we cannot use
            // compute_and_set_domain
            let top = this.px.h;
            let bottom = this.px.l;
            if (top === -1 || bottom === -1) {
                top = this.px.o;
                bottom = this.px.c;
            }
            min = d3.min(this.mark_data.map((d) => {
                return d[1][bottom] < d[1][top] ? d[1][bottom] : d[1][top];
            }));
            max = d3.max(this.mark_data.map((d) => {
                return d[1][top] > d[1][bottom] ? d[1][top] : d[1][bottom];
            }));
            if (max instanceof Date) {
                max = max.getTime();
            }
            yScale.set_domain([min - max_y_height, max + max_y_height], this.model_id + '_y');
        }
        else {
            yScale.del_domain([], this.model_id + '_y');
        }
    }
    get_data_dict(data, index) {
        const return_val = {
            index: index,
            x: data.x,
        };
        ['open', 'low', 'high', 'close'].forEach((str) => {
            return_val[str] = data.y[this.px[str.substr(0, 1)]];
        });
        return return_val;
    }
}
exports.OHLCModel = OHLCModel;
OHLCModel.serializers = Object.assign(Object.assign({}, MarkModel_1.MarkModel.serializers), { x: serialize.array_or_json, y: serialize.array_or_json });
//# sourceMappingURL=OHLCModel.js.map