veritatisquam
Version:
Carbon charting components, lerna monorepo
238 lines (204 loc) • 5.95 kB
text/typescript
// Internal Imports
import {
ChartConfig,
BaseChartOptions,
LayoutGrowth,
LayoutDirection,
LegendOrientations,
Events as ChartEvents,
} from './interfaces';
// Misc
import { ChartModel } from './model';
import {
Component,
Title,
Legend,
LayoutComponent,
Tooltip,
Spacer,
} from './components';
import { Tools } from './tools';
// Services
import { DOMUtils, Events, GradientUtils, Transitions } from './services/index';
export class Chart {
components: Component[];
services: any = {
domUtils: DOMUtils,
events: Events,
gradientUtils: GradientUtils,
transitions: Transitions,
};
model: ChartModel = new ChartModel(this.services);
constructor(holder: Element, chartConfigs: ChartConfig<BaseChartOptions>) {}
// Contains the code that uses properties that are overridable by the super-class
init(holder: Element, chartConfigs: ChartConfig<BaseChartOptions>) {
// Store the holder in the model
this.model.set({ holder }, { skipUpdate: true });
// Initialize all services
Object.keys(this.services).forEach((serviceName) => {
const serviceObj = this.services[serviceName];
this.services[serviceName] = new serviceObj(
this.model,
this.services
);
});
// Call update() when model has been updated
this.services.events.addEventListener(ChartEvents.Model.UPDATE, (e) => {
const animate = !!Tools.getProperty(e, 'detail', 'animate');
this.update(animate);
});
// Set model data & options
this.model.setData(chartConfigs.data);
// Set chart resize event listener
this.services.events.addEventListener(ChartEvents.Chart.RESIZE, () => {
this.update(false);
});
this.components = this.getComponents();
this.update();
}
getComponents(): any[] {
console.error('getComponents() method is not implemented');
return null;
}
update(animate = true) {
if (!this.components) {
return;
}
// Update all services
Object.keys(this.services).forEach((serviceName) => {
const serviceObj = this.services[serviceName];
serviceObj.update();
});
// Render all components
this.components.forEach((component) => component.render(animate));
// Asynchronously dispatch a "render-finished" event
// This is needed because of d3-transitions
// Since at the start of the transition
// Elements do not hold their final size or position
const pendingTransitions = this.services.transitions.getPendingTransitions();
const promises = Object.keys(pendingTransitions).map((transitionID) => {
const transition = pendingTransitions[transitionID];
return transition.end().catch((e) => e); // Skip rejects since we don't care about those;
});
Promise.all(promises).then(() =>
this.services.events.dispatchEvent(
ChartEvents.Chart.RENDER_FINISHED
)
);
}
destroy() {
// Call the destroy() method on all components
this.components.forEach((component) => component.destroy());
// Remove the chart holder
this.services.domUtils.getHolder().remove();
this.model.set({ destroyed: true }, { skipUpdate: true });
}
protected getChartComponents(graphFrameComponents: any[]) {
const titleComponent = {
id: 'title',
components: [new Title(this.model, this.services)],
growth: {
x: LayoutGrowth.PREFERRED,
y: LayoutGrowth.FIXED,
},
};
const legendComponent = {
id: 'legend',
components: [new Legend(this.model, this.services)],
growth: {
x: LayoutGrowth.PREFERRED,
y: LayoutGrowth.FIXED,
},
};
const graphFrameComponent = {
id: 'graph-frame',
components: graphFrameComponents,
growth: {
x: LayoutGrowth.STRETCH,
y: LayoutGrowth.FIXED,
},
};
const isLegendEnabled =
this.model.getOptions().legend.enabled !== false;
// TODORF - REUSE BETWEEN AXISCHART & CHART
// Decide the position of the legend in reference to the chart
let fullFrameComponentDirection = LayoutDirection.COLUMN;
if (isLegendEnabled) {
const legendPosition = Tools.getProperty(
this.model.getOptions(),
'legend',
'position'
);
if (legendPosition === 'left') {
fullFrameComponentDirection = LayoutDirection.ROW;
if (!this.model.getOptions().legend.orientation) {
this.model.getOptions().legend.orientation =
LegendOrientations.VERTICAL;
}
} else if (legendPosition === 'right') {
fullFrameComponentDirection = LayoutDirection.ROW_REVERSE;
if (!this.model.getOptions().legend.orientation) {
this.model.getOptions().legend.orientation =
LegendOrientations.VERTICAL;
}
} else if (legendPosition === 'bottom') {
fullFrameComponentDirection = LayoutDirection.COLUMN_REVERSE;
}
}
const legendSpacerComponent = {
id: 'spacer',
components: [new Spacer(this.model, this.services)],
growth: {
x: LayoutGrowth.PREFERRED,
y: LayoutGrowth.FIXED,
},
};
const fullFrameComponent = {
id: 'full-frame',
components: [
new LayoutComponent(
this.model,
this.services,
[
...(isLegendEnabled ? [legendComponent] : []),
...(isLegendEnabled ? [legendSpacerComponent] : []),
graphFrameComponent,
],
{
direction: fullFrameComponentDirection,
}
),
],
growth: {
x: LayoutGrowth.STRETCH,
y: LayoutGrowth.FIXED,
},
};
// Add chart title if it exists
const topLevelLayoutComponents = [];
if (this.model.getOptions().title) {
topLevelLayoutComponents.push(titleComponent);
const titleSpacerComponent = {
id: 'spacer',
components: [new Spacer(this.model, this.services)],
growth: {
x: LayoutGrowth.PREFERRED,
y: LayoutGrowth.FIXED,
},
};
topLevelLayoutComponents.push(titleSpacerComponent);
}
topLevelLayoutComponents.push(fullFrameComponent);
return [
new Tooltip(this.model, this.services),
new LayoutComponent(
this.model,
this.services,
topLevelLayoutComponents,
{
direction: LayoutDirection.COLUMN,
}
),
];
}
}