text: import SvgTip from '../objects/SvgTip';
  import { $, isElementInViewport, getElementContentWidth } from '../utils/dom';
  import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText } from '../utils/draw';
  import { BASE_MEASURES, getExtraHeight, getExtraWidth, getTopOffset, getLeftOffset,
  	INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS} from '../utils/constants';
  import { getColor, isValidColor } from '../utils/colors';
  import { runSMILAnimation } from '../utils/animation';
  import { downloadFile, prepareForExport } from '../utils/export';
  
  let BOUND_DRAW_FN;
  
  export default class BaseChart {
  	constructor(parent, options) {
  
  		this.parent = typeof parent === 'string'
  			? document.querySelector(parent)
  			: parent;
  
  		if (!(this.parent instanceof HTMLElement)) {
  			throw new Error('No `parent` element to render on was provided.');
  		}
  
  		this.rawChartArgs = options;
  
  		this.title = options.title || '';
  		this.type = options.type || '';
  
  		this.realData = this.prepareData(options.data);
  		this.data = this.prepareFirstData(this.realData);
  
  		this.colors = this.validateColors(options.colors, this.type);
  
  		this.config = {
  			showTooltip: 1, // calculate
  			showLegend: 1, // calculate
  			isNavigable: options.isNavigable || 0,
  			animate: 1
  		};
  
  		this.measures = JSON.parse(JSON.stringify(BASE_MEASURES));
  		let m = this.measures;
  		this.setMeasures(options);
  		if(!this.title.length) { m.titleHeight = 0; }
  		if(!this.config.showLegend) m.legendHeight = 0;
  		this.argHeight = options.height || m.baseHeight;
  
  		this.state = {};
  		this.options = {};
  
  		this.initTimeout = INIT_CHART_UPDATE_TIMEOUT;
  
  		if(this.config.isNavigable) {
  			this.overlays = [];
  		}
  
  		this.configure(options);
  	}
  
  	prepareData(data) {
  		return data;
  	}
  
  	prepareFirstData(data) {
  		return data;
  	}
  
  	validateColors(colors, type) {
  		const validColors = [];
  		colors = (colors || []).concat(DEFAULT_COLORS[type]);
  		colors.forEach((string) => {
  			const color = getColor(string);
  			if(!isValidColor(color)) {
  				console.warn('"' + string + '" is not a valid color.');
  			} else {
  				validColors.push(color);
  			}
  		});
  		return validColors;
  	}
  
  	setMeasures() {
  		// Override measures, including those for title and legend
  		// set config for legend and title
  	}
  
  	configure() {
  		let height = this.argHeight;
  		this.baseHeight = height;
  		this.height = height - getExtraHeight(this.measures);
  
  		// Bind window events
  		BOUND_DRAW_FN = this.boundDrawFn.bind(this);
  		window.addEventListener('resize', BOUND_DRAW_FN);
  		window.addEventListener('orientationchange', this.boundDrawFn.bind(this));
  	}
  
  	boundDrawFn() {
  		this.draw(true);
  	}
  
  	unbindWindowEvents() {
  		window.removeEventListener('resize', BOUND_DRAW_FN);
  		window.removeEventListener('orientationchange', this.boundDrawFn.bind(this));
  	}
  
  	// Has to be called manually
  	setup() {
  		this.makeContainer();
  		this.updateWidth();
  		this.makeTooltip();
  
  		this.draw(false, true);
  	}
  
  	makeContainer() {
  		// Chart needs a dedicated parent element
  		this.parent.innerHTML = '';
  
  		let args = {
  			inside: this.parent,
  			className: 'chart-container'
  		};
  
  		if(this.independentWidth) {
  			args.styles = { width: this.independentWidth + 'px' };
  		}
  
  		this.container = $.create('div', args);
  	}
  
  	makeTooltip() {
  		this.tip = new SvgTip({
  			parent: this.container,
  			colors: this.colors
  		});
  		this.bindTooltip();
  	}
  
  	bindTooltip() {}
  
  	draw(onlyWidthChange=false, init=false) {
  		this.updateWidth();
  
  		this.calc(onlyWidthChange);
  		this.makeChartArea();
  		this.setupComponents();
  
  		this.components.forEach(c => c.setup(this.drawArea));
  		// this.components.forEach(c => c.make());
  		this.render(this.components, false);
  
  		if(init) {
  			this.data = this.realData;
  			setTimeout(() => {this.update(this.data);}, this.initTimeout);
  		}
  
  		this.renderLegend();
  
  		this.setupNavigation(init);
  	}
  
  	calc() {} // builds state
  
  	updateWidth() {
  		this.baseWidth = getElementContentWidth(this.parent);
  		this.width = this.baseWidth - getExtraWidth(this.measures);
  	}
  
  	makeChartArea() {
  		if(this.svg) {
  			this.container.removeChild(this.svg);
  		}
  		let m = this.measures;
  
  		this.svg = makeSVGContainer(
  			this.container,
  			'frappe-chart chart',
  			this.baseWidth,
  			this.baseHeight
  		);
  		this.svgDefs = makeSVGDefs(this.svg);
  
  		if(this.title.length) {
  			this.titleEL = makeText(
  				'title',
  				m.margins.left,
  				m.margins.top,
  				this.title,
  				{
  					fontSize: m.titleFontSize,
  					fill: '#666666',
  					dy: m.titleFontSize
  				}
  			);
  		}
  
  		let top = getTopOffset(m);
  		this.drawArea = makeSVGGroup(
  			this.type + '-chart chart-draw-area',
  			`translate(${getLeftOffset(m)}, ${top})`
  		);
  
  		if(this.config.showLegend) {
  			top += this.height + m.paddings.bottom;
  			this.legendArea = makeSVGGroup(
  				'chart-legend',
  				`translate(${getLeftOffset(m)}, ${top})`
  			);
  		}
  
  		if(this.title.length) { this.svg.appendChild(this.titleEL); }
  		this.svg.appendChild(this.drawArea);
  		if(this.config.showLegend) { this.svg.appendChild(this.legendArea); }
  
  		this.updateTipOffset(getLeftOffset(m), getTopOffset(m));
  	}
  
  	updateTipOffset(x, y) {
  		this.tip.offset = {
  			x: x,
  			y: y
  		};
  	}
  
  	setupComponents() { this.components = new Map(); }
  
  	update(data) {
  		if(!data) {
  			console.error('No data to update.');
  		}
  		this.data = this.prepareData(data);
  		this.calc(); // builds state
  		this.render();
  	}
  
  	render(components=this.components, animate=true) {
  		if(this.config.isNavigable) {
  			// Remove all existing overlays
  			this.overlays.map(o => o.parentNode.removeChild(o));
  			// ref.parentNode.insertBefore(element, ref);
  		}
  		let elementsToAnimate = [];
  		// Can decouple to this.refreshComponents() first to save animation timeout
  		components.forEach(c => {
  			elementsToAnimate = elementsToAnimate.concat(c.update(animate));
  		});
  		if(elementsToAnimate.length > 0) {
  			runSMILAnimation(this.container, this.svg, elementsToAnimate);
  			setTimeout(() => {
  				components.forEach(c => c.make());
  				this.updateNav();
  			}, CHART_POST_ANIMATE_TIMEOUT);
  		} else {
  			components.forEach(c => c.make());
  			this.updateNav();
  		}
  	}
  
  	updateNav() {
  		if(this.config.isNavigable) {
  			this.makeOverlay();
  			this.bindUnits();
  		}
  	}
  
  	renderLegend() {}
  
  	setupNavigation(init=false) {
  		if(!this.config.isNavigable) return;
  
  		if(init) {
  			this.bindOverlay();
  
  			this.keyActions = {
  				'13': this.onEnterKey.bind(this),
  				'37': this.onLeftArrow.bind(this),
  				'38': this.onUpArrow.bind(this),
  				'39': this.onRightArrow.bind(this),
  				'40': this.onDownArrow.bind(this),
  			};
  
  			document.addEventListener('keydown', (e) => {
  				if(isElementInViewport(this.container)) {
  					e = e || window.event;
  					if(this.keyActions[e.keyCode]) {
  						this.keyActions[e.keyCode]();
  					}
  				}
  			});
  		}
  	}
  
  	makeOverlay() {}
  	updateOverlay() {}
  	bindOverlay() {}
  	bindUnits() {}
  
  	onLeftArrow() {}
  	onRightArrow() {}
  	onUpArrow() {}
  	onDownArrow() {}
  	onEnterKey() {}
  
  	addDataPoint() {}
  	removeDataPoint() {}
  
  	getDataPoint() {}
  	setCurrentDataPoint() {}
  
  	updateDataset() {}
  
  	export() {
  		let chartSvg = prepareForExport(this.svg);
  		downloadFile(this.title || 'Chart', [chartSvg]);
  	}
  }
  