Spaces:
Runtime error
Runtime error
File size: 7,677 Bytes
63858e7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
/**
* Created by Hendrik Strobelt (hendrik.strobelt.com) on 12/3/16.
* Modified by Ben Hoover on 4/16/2019
*/
import * as d3 from 'd3'
import {D3Sel, Util} from "../etc/Util";
import {SimpleEventHandler} from "../etc/SimpleEventHandler";
import {SVG} from "../etc/SVGplus";
/**
* Should have VComponentHTML and VComponentSVG
*
* Common Properties:
* - events
* - eventHandler (V important)
* - options (Maintains public state. Can expose these with get/set functions with auto update)
* - _current (Maintains private state)
* - cssName (synced with corresponding CSS file)
* - parent (HTML is div containing the base, SVG is SVG element)
* - base (HTML is div with css_name established)
* - _data (Data used to create and render the component)
* - _renderData (Data needed to display. This may not be needed, but is currently used in histogram)
*
* Common Methods:
* - constructor
* - _render() Consider replacing with `_updateData()` that updates all data at once
* - update() Consider replacing this with `data()` that auto updates data
* - redraw()
* - destroy()
*/
export abstract class VComponent<DataInterface> {
// STATIC FIELDS ============================================================
/**
* The static property that contains all class related events.
* Should be overwritten and event strings have to be unique!!
*/
static events: {} = {noEvent: 'VComponent_noEvent'};
/**
* Defines the layers in SVG for bg,main,fg,...
*/
// protected abstract readonly layout: { name: string, pos: number[] }[] = [{name: 'main', pos: [0, 0]}];
protected id: string; // Mostly obsolete, nice to have simple ID to assign in CSS
protected parent: D3Sel;
protected abstract options: { [key: string]: any };
protected base: D3Sel; // Mostly obsolete, represents <g> in svg
protected layers: { main?: D3Sel, fg?: D3Sel, bg?: D3Sel, [key: string]: D3Sel }; // Still useful
protected eventHandler: SimpleEventHandler;
protected _visibility: { hidden: boolean, hideElement?: D3Sel | null; [key: string]: any }; // Enables transitions from visible to invisible. Mostly obsolete.
protected _data: DataInterface;
protected renderData: any; // Unnecessary
protected abstract css_name: string; // Make the same as the corresponding css file
protected abstract _current: {}; // Private state information contained in the object itself.
// CONSTRUCTOR ============================================================
/**
* Simple constructor. Subclasses should call @superInit(options) as well.
* see why here: https://stackoverflow.com/questions/43595943/why-are-derived-class-property-values-not-seen-in-the-base-class-constructor
*
* template:
constructor(d3Parent: D3Sel, eventHandler?: SimpleEventHandler, options: {} = {}) {
super(d3Parent, eventHandler);
// -- access to subclass params:
this.superInit(options);
}
*
* @param {D3Sel} d3parent D3 selection of parent SVG DOM Element
* @param {SimpleEventHandler} eventHandler a global event handler object or 'null' for local event handler
*/
protected constructor(d3parent: D3Sel, eventHandler?: SimpleEventHandler) {
this.id = Util.simpleUId({});
this.parent = d3parent;
// If not further specified - create a local event handler bound to the bas element
this.eventHandler = eventHandler ||
new SimpleEventHandler(this.parent.node());
// Object for storing internal states and variables
this._visibility = {hidden: false};
}
protected superInitHTML(options: {} = {}) {
Object.keys(options).forEach(key => this.options[key] = options[key]);
this.base = this.parent.append('div')
.classed(this.css_name, true)
}
/**
* Has to be called as last call in subclass constructor.
*
* @param {{}} options
* @param defaultLayers -- create the default <g> layers: bg -> main -> fg
*/
protected superInitSVG(options: {} = {}, defaultLayers = ['bg', 'main', 'fg']) {
// Set default options if not specified in constructor call
// const defaults = this.defaultOptions;
// this.options = {};
// const keys = new Set([...Object.keys(defaults), ...Object.keys(options)]);
// keys.forEach(key => this.options[key] = (key in options) ? options[key] : defaults[key]);
Object.keys(options).forEach(key => this.options[key] = options[key]);
this.layers = {};
// Create the base group element
const svg = this.parent;
this.base = SVG.group(svg,
this.css_name + ' ID' + this.id,
this.options.pos);
// create default layers: background, main, foreground
if (defaultLayers) {
// construction order is important !
defaultLayers.forEach(layer =>{
this.layers[layer] = SVG.group(this.base, layer);
});
}
}
/**
* Should be overwritten to create the static DOM elements
* @abstract
* @return {*} ---
*/
protected abstract _init();
// DATA UPDATE & RENDER ============================================================
/**
* Every time data has changed, update is called and
* triggers wrangling and re-rendering
* @param {Object} data data object
* @return {*} ---
*/
update(data: DataInterface) {
this._data = data;
if (this._visibility.hidden) return;
this.renderData = this._wrangle(data);
this._render(this.renderData);
}
/**
* Data wrangling method -- implement in subclass. Returns this.renderData.
* Simplest implementation: `return data;`
* @param {Object} data data
* @returns {*} -- data in render format
* @abstract
*/
protected abstract _wrangle(data);
/**
* Is responsible for mapping data to DOM elements
* @param {Object} renderData pre-processed (wrangled) data
* @abstract
* @returns {*} ---
*/
protected abstract _render(renderData): void;
// UPDATE OPTIONS ============================================================
/**
* Updates instance options
* @param {Object} options only the options that should be updated
* @param {Boolean} reRender if option change requires a re-rendering (default:false)
* @returns {*} ---
*/
updateOptions({options, reRender = false}) {
Object.keys(options).forEach(k => this.options[k] = options[k]);
if (reRender) this._render(this.renderData);
}
// === CONVENIENCE ====
redraw(){
this._render(this.renderData);
}
setHideElement(hE: D3Sel) {
this._visibility.hideElement = hE;
}
hideView() {
if (!this._visibility.hidden) {
const hE = this._visibility.hideElement || this.parent;
hE.transition().styles({
'opacity': 0,
'pointer-events': 'none'
}).style('display', 'none');
this._visibility.hidden = true;
}
}
unhideView() {
if (this._visibility.hidden) {
const hE = this._visibility.hideElement || this.parent;
hE.transition().styles({
'opacity': 1,
'pointer-events': null,
'display': null
});
this._visibility.hidden = false;
// this.update(this.data);
}
}
destroy() {
this.base.remove();
}
clear() {
this.base.html('');
}
} |