import * as d3 from 'd3';
import * as _ from 'lodash';
import { baseCSS } from './OWIDChartCSS';
import {
config
} from './OWIDChartConfig';
/**
* Base class for chart components
*
* This object will create a
DOM element that contains the
*
*
*
*/
export class OWIDChart {
protected _data: [] = [];
protected _marginTop: number = config.marginTop;
protected _marginBottom: number = config.marginBottom;
protected _marginLeft: number = config.marginLeft;
protected _marginRight: number = config.marginRight;
protected _heightTotal: number = config.heightTotal;
protected _widthTotal: number = config.widthTotal;
/** _height / _width refer to internal visualization area ( maincontainer within ) */
protected _height: number;
protected _width: number;
protected _unit: string;
protected _className: string;
protected _valuesRange: [any, any];
protected _dimensions: { years: any; entities: any; };
protected _scaleColor: d3.ScaleOrdinal;
protected _mainDivContainer: d3.Selection;
protected _mainContainer: d3.Selection ;
protected _chartSVG: any;
protected _toolTip: any;
protected _colorScale: any;
protected _chartContent: any;
protected _x: any;
protected _y: any;
constructor(data: any, options: any) {
this._data = data;
this._widthTotal = (options && options.width) || this._widthTotal;
this._heightTotal = (options && options.height) || this._heightTotal;
this._marginTop = (options && options.marginTop) || this._marginTop;
this._marginBottom = (options && options.marginBottom) || this._marginBottom;
this._marginLeft = (options && options.marginLeft) || this._marginLeft;
this._marginTop = (options && options.marginTop) || this._marginTop;
// Redefine main visialization height / width according to current total heigh/width and margins
this._height = this._heightTotal - this._marginBottom - this._marginTop;
this._width = this._widthTotal - this._marginLeft - this._marginRight;
this._y = (options && options.y) || {};
this._x = (options && options.x) || {};
this._unit = (options && options.unit) || "";
this._className = "owidChart";
this._unit = (options && options.unit) || "";
this._valuesRange = d3.extent(this._data, (d: any) => d.value);
this._dimensions = {
years: (options && options.years) || this.getDimensionValues("year"),
entities:
(options && options.enitites) || this.getDimensionValues("entityName")
};
this._scaleColor = d3.scaleOrdinal(config.colorScheme);
this._mainDivContainer = d3
.create("div")
.attr("class", "chart container")
.attr("style", "position: relative; clear: both;");
this._chartSVG = this._mainDivContainer
.append("svg");
this._mainContainer = this._chartSVG.append("g")
this.setupSVGElements();
this.baseStartupSettings();
}
setupSVGElements(): void {
this._chartSVG
.attr("class", `chart container ${this._className}`)
.attr("fill", "currentColor")
.attr("font-family", "system-ui, sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "middle")
.attr("width", this._widthTotal)
.attr("height", this._heightTotal)
.attr(
"viewBox",
`0 0 ${this._widthTotal} ${this._heightTotal}`
)
.call((svg:any) => svg.append("style").text(this.css()))
/*.call((svg:any) =>
svg
.append("rect")
.attr("class","bgLayer")
.attr("width", this._widthTotal)
.attr("height", this._heightTotal)
.attr("fill", "white")
);*/
// If it does not already exists, we add a element that will be the main container
this._mainContainer
.attr("class", "chart main container")
.attr("transform", `translate(${this._marginLeft}, ${this._marginTop})`)
.call((g:any) =>
g
.append("rect")
.attr("class", "backgroundLayer")
.attr("width", this._width)
.attr("height", this._height)
.attr("fill", "white")
)
.call((g:any) =>
g
.append("g")
.attr("class", "axis x")
.attr("transform", `translate(${0}, ${this._height})`)
)
.call((g:any) =>
g
.append("g")
.attr("class", "axis y")
.attr("transform", `translate(0,0)`)
);
}
/**
* startupSettings()
* Updated charts internat height / width dimensons based on current margins
*
* This fucntion can / should be called after there has been a change in configurations (e.g. width)
*/
protected baseStartupSettings(): void {
this._chartSVG
.attr("width", this._widthTotal)
.attr("height", this._heightTotal)
.attr(
"viewBox",
`0 0 ${this._widthTotal} ${this._heightTotal}`
)
this._chartSVG
.select("rect.bgLayer")
.attr("width", this._widthTotal)
.attr("height", this._heightTotal);
this._chartSVG
.select("g.container")
.attr("transform", `translate(${this._marginLeft}, ${this._marginTop})`);
this._chartSVG
.select("g.main.container")
.select("rect.backgroundLayer")
.attr("width", this._width)
.attr("height", this._height);
this._chartSVG
.select("g.main.container")
.select("g.axis.x")
.attr("transform", `translate(${0}, ${this._height})`)
// Redefine main visialization height / width according to current total heigh/width and margins
this._height = this._heightTotal - this._marginBottom - this._marginTop;
this._width = this._widthTotal - this._marginLeft - this._marginRight;
// Applies new left margin to our chart main container
this._chartSVG
.select("g.container")
.attr("transform", `translate(${this._marginLeft}, ${this._marginTop})`);
this._mainContainer
.select("rect.backgroundLayer")
.attr("width", this._width)
.attr("height", this._height);
this._chartSVG
.select("g.axis.x")
.attr("transform", `translate(${0}, ${this._height})`)
}
protected startupSettings() {
this.baseStartupSettings();
}
/**
* Gets / sets the data source
* @param data
* @returns current data | current OWIDBarChart object
*/
data(data: []): OWIDChart | [] {
if (arguments.length) {
this._data = data;
this.startupSettings();
this.render();
return this;
} else {
return this._data
}
}
/**
* Gets / sets the total chart width
* @param width
* @returns current width | current OWIDBarChart object
*/
width(width: number): OWIDChart | number {
if (arguments.length) {
this._widthTotal = width;
this.startupSettings();
this.render();
return this;
} else {
return this._widthTotal
}
}
/**
* Gets / sets the unit associated to values in our data
* @param unit
* @returns current unit | current OWIDBarChart object
*/
unit(unit: string): OWIDChart | string {
if (arguments.length) {
this._unit = unit;
this.startupSettings();
this.render();
return this;
} else {
return this._unit
}
}
/**
* Gets / sets the option for
* @param options
* @returns current unit | current OWIDBarChart object
*/
x(options: {}): OWIDChart | {} {
if (arguments.length) {
this._x = options;
this.startupSettings();
this.render();
return this;
} else {
return this._x
}
}
/**
* Gets / sets the option for
* @param options
* @returns current unit | current OWIDBarChart object
*/
y(options: {}): OWIDChart | {} {
if (arguments.length) {
this._y = options;
this.startupSettings();
this.render();
return this;
} else {
return this._y
}
}
/**
* Extracts from the data the collection of unique values for a given dimension (e.g. years / entityNames)
*
* @param dimension dimension included in the data ("year" | "entityName")
* @returns array with dimension values
*/
protected getDimensionValues(dimension: string): any {
return _.chain(this._data)
.map((d: { [x: string]: any; }) => d[dimension])
.uniq()
.value();
}
/**
* Auxiliary function that gets the size of a text given the text / fontSize and fontFace
*
* @param text
* @param fontSize
* @param fontFace
* @returns estinated text width
*/
protected getTextWidth(text: any, fontSize: string | number, fontFace: string): number {
const canvas = document.createElement("canvas"),
context = canvas.getContext("2d");
let textWidth = null;
if (context) {
context.font = fontSize + "px " + fontFace;
textWidth = context.measureText(text).width
}
return textWidth as number;
}
render() {
}
node() {
return this._mainDivContainer.node();
}
css(): string {
return baseCSS;
}
}