@tanstack/charts
Version:
A chart grammar for TypeScript and JavaScript. Marks consume your data directly, channels describe visual encodings, and the engine compiles them into a renderer-neutral keyed scene. TanStack's compact scales cover common numeric and categorical mappings.
351 lines (346 loc) • 14.9 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, TemplateRef, Input, Directive, APP_ID, Injectable, afterNextRender, ViewContainerRef, ContentChild, ViewChild, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { resolveChartAdapterLayout } from '@tanstack/charts/adapter';
import { createChartRendererAdapter } from '@tanstack/charts/adapter/renderer';
import { renderChartSvg } from '@tanstack/charts/svg';
import { createSvgChartRenderer } from '@tanstack/charts/svg/renderer';
class ChartTooltipBodyDirective {
constructor() {
this.templateRef = inject(TemplateRef);
}
static ngTemplateContextGuard(_directive, context) {
return true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChartTooltipBodyDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: ChartTooltipBodyDirective, isStandalone: true, selector: "ng-template[tanstackChartTooltipBody]", inputs: { definition: ["tanstackChartTooltipBody", "definition"] }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChartTooltipBodyDirective, decorators: [{
type: Directive,
args: [{
selector: 'ng-template[tanstackChartTooltipBody]',
standalone: true,
}]
}], propDecorators: { definition: [{
type: Input,
args: [{ required: true, alias: 'tanstackChartTooltipBody' }]
}] } });
class ChartIdGenerator {
constructor() {
this.appId = inject(APP_ID);
this.nextId = 0;
}
next() {
return `ts-chart-${this.appId}-${++this.nextId}`.replaceAll(/[^a-zA-Z0-9_-]/g, '');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChartIdGenerator, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChartIdGenerator, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChartIdGenerator, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
class Chart {
set tooltipBodyDirective(directive) {
if (directive === this.tooltipBody)
return;
this.destroyTooltipBodyView();
this.tooltipBody = directive;
this.updateAdapter();
if (directive && this.tooltipBodyTarget) {
this.renderTooltipBody(this.tooltipBodyTarget);
}
}
constructor() {
this.initialMarkup = '';
this.hostClass = 'ts-chart-host';
this.hostStyle = 'position:relative;width:100%;height:320px';
this.sanitizer = inject(DomSanitizer);
this.generatedId = inject(ChartIdGenerator).next();
this.handleTooltipBodyChange = (target) => {
if (!target) {
this.tooltipBodyTarget = undefined;
this.destroyTooltipBodyView();
return;
}
this.tooltipBodyTarget = target;
this.renderTooltipBody(target);
};
afterNextRender(() => {
this.adapter?.mount(this.surface.nativeElement);
});
}
ngOnChanges(_changes) {
if (!this.options)
return;
const layout = resolveChartAdapterLayout(this.options);
this.hostClass = this.options.class
? `ts-chart-host ${this.options.class}`
: 'ts-chart-host';
this.hostStyle = [
'position:relative',
`width:${this.options.width === undefined ? '100%' : `${this.options.width}px`}`,
this.options.height !== undefined
? `height:${this.options.height}px`
: layout.aspectRatio === undefined
? 'height:320px'
: `aspect-ratio:${layout.aspectRatio}`,
this.options.style,
]
.filter(Boolean)
.join(';');
this.updateAdapter();
}
ngOnDestroy() {
this.adapter?.destroy();
this.destroyTooltipBodyView();
}
updateAdapter() {
if (!this.options)
return;
const hostOptions = toHostOptions(this.options, this.options.idPrefix ?? this.generatedId, this.resolveRenderer(this.options.renderSvg ?? renderChartSvg), this.tooltipBody ? this.handleTooltipBodyChange : undefined);
if (!this.adapter) {
this.adapter = createChartRendererAdapter(hostOptions);
this.initialMarkup = this.sanitizer.bypassSecurityTrustHtml(this.adapter.prerender());
}
else {
this.adapter.update(hostOptions);
}
}
resolveRenderer(renderSvg) {
if (!this.renderer || renderSvg !== this.activeRenderSvg) {
this.activeRenderSvg = renderSvg;
this.renderer = createSvgChartRenderer(renderSvg);
}
return this.renderer;
}
renderTooltipBody(target) {
const directive = this.tooltipBody;
if (!directive)
return;
this.defaultTooltipText =
typeof target.content === 'string' ? target.content : undefined;
this.defaultTooltipContent =
typeof target.content === 'string' ? undefined : target.content;
const context = this.tooltipBodyView?.context;
if (context) {
context.points = target.points;
context.content = target.content;
context.defaultBody = this.defaultTooltipBody;
context.pinned = target.pinned;
context.dismiss = target.dismiss;
this.moveTooltipBodyView(target.element);
this.tooltipBodyView?.detectChanges();
return;
}
const nextContext = {
points: target.points,
content: target.content,
defaultBody: this.defaultTooltipBody,
pinned: target.pinned,
dismiss: target.dismiss,
};
nextContext.$implicit = nextContext;
this.tooltipBodyView = this.tooltipOutlet.createEmbeddedView(directive.templateRef, nextContext);
this.moveTooltipBodyView(target.element);
this.tooltipBodyView.detectChanges();
}
moveTooltipBodyView(target) {
for (const node of this.tooltipBodyView?.rootNodes ?? []) {
target.append(node);
}
}
destroyTooltipBodyView() {
const view = this.tooltipBodyView;
if (!view)
return;
const nodes = [...view.rootNodes];
const index = this.tooltipOutlet.indexOf(view);
if (index === -1)
view.destroy();
else
this.tooltipOutlet.remove(index);
for (const node of nodes)
node.parentNode?.removeChild(node);
this.tooltipBodyView = undefined;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: Chart, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: Chart, isStandalone: true, selector: "tanstack-chart", inputs: { options: "options" }, queries: [{ propertyName: "tooltipBodyDirective", first: true, predicate: ChartTooltipBodyDirective, descendants: true }], viewQueries: [{ propertyName: "surface", first: true, predicate: ["surface"], descendants: true, static: true }, { propertyName: "tooltipOutlet", first: true, predicate: ["tooltipOutlet"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "defaultTooltipBody", first: true, predicate: ["defaultTooltipBody"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
<div [class]="hostClass" [style]="hostStyle">
<div
#surface
class="ts-chart-surface"
style="width: 100%; height: 100%"
[innerHTML]="initialMarkup"
></div>
</div>
<ng-container #tooltipOutlet></ng-container>
<ng-content select="ng-template[tanstackChartTooltipBody]" />
<ng-template #defaultTooltipBody>
(defaultTooltipText !== undefined) {
{{ defaultTooltipText }}
} if (defaultTooltipContent; as content) {
(content.title) {
<div
class="ts-chart-tooltip__title"
style="display:flex;align-items:center;gap:0.4rem;font-weight:650"
[style.margin-bottom]="content.rows.length ? '0.3rem' : '0'"
>
(content.color) {
<span
class="ts-chart-tooltip__swatch"
aria-hidden="true"
style="display:block;width:0.55rem;height:0.55rem;border-radius:0.15rem;box-shadow:inset 0 0 0 1px rgb(0 0 0/.12)"
[style.background]="content.color"
></span>
}
{{ content.title }}
</div>
}
(content.rows.length) {
<div class="ts-chart-tooltip__rows" aria-hidden="true">
(row of content.rows; track $index) {
<div
class="ts-chart-tooltip__row"
style="display:grid;grid-template-columns:0.55rem minmax(0,1fr) auto;align-items:center;column-gap:0.4rem"
>
(row.color) {
<span
class="ts-chart-tooltip__swatch"
aria-hidden="true"
style="display:block;width:0.55rem;height:0.55rem;border-radius:0.15rem;box-shadow:inset 0 0 0 1px rgb(0 0 0/.12)"
[style.background]="row.color"
></span>
} {
<span></span>
}
<span>{{ row.label }}</span>
<span
style="text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap"
>{{ row.value }}</span
>
</div>
}
</div>
}
}
</ng-template>
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: Chart, decorators: [{
type: Component,
args: [{
selector: 'tanstack-chart',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
template: `
<div [class]="hostClass" [style]="hostStyle">
<div
#surface
class="ts-chart-surface"
style="width: 100%; height: 100%"
[innerHTML]="initialMarkup"
></div>
</div>
<ng-container #tooltipOutlet></ng-container>
<ng-content select="ng-template[tanstackChartTooltipBody]" />
<ng-template #defaultTooltipBody>
(defaultTooltipText !== undefined) {
{{ defaultTooltipText }}
} if (defaultTooltipContent; as content) {
(content.title) {
<div
class="ts-chart-tooltip__title"
style="display:flex;align-items:center;gap:0.4rem;font-weight:650"
[style.margin-bottom]="content.rows.length ? '0.3rem' : '0'"
>
(content.color) {
<span
class="ts-chart-tooltip__swatch"
aria-hidden="true"
style="display:block;width:0.55rem;height:0.55rem;border-radius:0.15rem;box-shadow:inset 0 0 0 1px rgb(0 0 0/.12)"
[style.background]="content.color"
></span>
}
{{ content.title }}
</div>
}
(content.rows.length) {
<div class="ts-chart-tooltip__rows" aria-hidden="true">
(row of content.rows; track $index) {
<div
class="ts-chart-tooltip__row"
style="display:grid;grid-template-columns:0.55rem minmax(0,1fr) auto;align-items:center;column-gap:0.4rem"
>
(row.color) {
<span
class="ts-chart-tooltip__swatch"
aria-hidden="true"
style="display:block;width:0.55rem;height:0.55rem;border-radius:0.15rem;box-shadow:inset 0 0 0 1px rgb(0 0 0/.12)"
[style.background]="row.color"
></span>
} {
<span></span>
}
<span>{{ row.label }}</span>
<span
style="text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap"
>{{ row.value }}</span
>
</div>
}
</div>
}
}
</ng-template>
`,
}]
}], ctorParameters: () => [], propDecorators: { options: [{
type: Input,
args: [{ required: true }]
}], surface: [{
type: ViewChild,
args: ['surface', { static: true }]
}], tooltipOutlet: [{
type: ViewChild,
args: ['tooltipOutlet', { read: ViewContainerRef, static: true }]
}], defaultTooltipBody: [{
type: ViewChild,
args: ['defaultTooltipBody', { static: true }]
}], tooltipBodyDirective: [{
type: ContentChild,
args: [ChartTooltipBodyDirective]
}] } });
function toHostOptions(options, idPrefix, renderer, onTooltipBodyChange) {
const { class: _class, style: _style, renderSvg: _renderSvg, onRender, ...hostOptions } = options;
return {
...hostOptions,
idPrefix,
renderer,
onRender: adaptOnRender(onRender),
onTooltipBodyChange,
};
}
function adaptOnRender(onRender) {
if (!onRender)
return undefined;
return (context) => {
const svg = context.surface.element;
const SvgElement = context.container.ownerDocument.defaultView?.SVGSVGElement;
if (!SvgElement || !(svg instanceof SvgElement)) {
throw new TypeError('Expected the SVG chart surface.');
}
onRender({
container: context.container,
scene: context.scene,
svg,
interaction: context.interaction,
});
};
}
/**
* Generated bundle index. Do not edit.
*/
export { Chart, ChartTooltipBodyDirective };
//# sourceMappingURL=tanstack-angular-charts.mjs.map