UNPKG

igniteui-react-charts

Version:

Ignite UI React charting components for building rich data visualizations using TypeScript APIs.

654 lines (645 loc) 23 kB
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { ReactRenderer, ReactWrapper } from "igniteui-react-core"; import { TypeRegistrar } from "igniteui-react-core"; import { DataChartStylingDefaults } from './DataChartStylingDefaults'; import { XamDoughnutChart } from './XamDoughnutChart'; import { ensureBool, fromPoint, CollectionAdapter, initializePropertiesFromCss, NamePatcher, isValidProp, getModifiedProps, toSpinal } from "igniteui-react-core"; import { Style } from "igniteui-react-core"; import { IgrSliceClickEventArgs } from "./igr-slice-click-event-args"; import { IgrHoleDimensionsChangedEventArgs } from "./igr-hole-dimensions-changed-event-args"; import { RingSeriesBase } from "./RingSeriesBase"; import { NotifyCollectionChangedAction } from "igniteui-react-core"; import { IgrRingSeriesCollection } from "./igr-ring-series-collection"; import { IgrDataContext } from "igniteui-react-core"; import { delegateCombine, delegateRemove } from "igniteui-react-core"; import { ContentChildrenManager } from "igniteui-react-core"; /** * Represents concentric circles divided on arcs depending on data. */ export class IgrDoughnutChart extends React.Component { set height(value) { this._height = value; if (this._elRef) { this._elRef.style.height = value; this._chart.notifyResized(); } } get height() { return this._height; } set width(value) { this._width = value; if (this._elRef) { this._elRef.style.width = value; this._chart.notifyResized(); } } get width() { return this._width; } _updateContentChildren() { this.contentSeries = []; let contentChildrenActual = this._contentChildrenManager.contentChildrenActual; for (let i = 0; i < contentChildrenActual.length; i++) { if ((RingSeriesBase.$type).isAssignableFrom(contentChildrenActual[i].i.$type)) { this.contentSeries.push(contentChildrenActual[i]); } } if (this._seriesAdapter !== null) { this._seriesAdapter.notifyContentChanged(); } } _tooltipRef(t) { //console.log(t); if (t === null) { return; } if (t.currentOwner && t.currentOwner.tooltipTemplate) { t.template = t.currentOwner.tooltipTemplate; } this._activeTooltips.set(t.currentOwner, t); } render() { // if (!this._childrenDiffer(this.props.children)) { // let div = React.createElement("div", { // ref: (ref) => { // this._elRef = ref; // }, // children: this.props.children // }); // return div; // } else { let children = this._contentChildrenManager.getChildren(this.props.children); if (this._currentTooltips && this._currentTooltips.length > 0) { for (let i = 0; i < this._currentTooltips.length; i++) { let t = this._currentTooltips[i]; if (this._activeTooltipElements.has(t)) { children.push(this._activeTooltipElements.get(t)); } else { if (!TypeRegistrar.isRegistered("IgrTooltipContainer")) { continue; } let Tooltip = TypeRegistrar.get("IgrTooltipContainer"); let tEle = React.createElement(Tooltip, { ref: this._tooltipRef, key: this._currentTooltips[i].key, owner: this._currentTooltips[i] }); let portal = ReactDOM.createPortal(tEle, t, this._currentTooltips[i].key); this._activeTooltipElements.set(t, portal); children.push(portal); } } } let div = React.createElement("div", { className: "ig-doughnut-chart igr-doughnut-chart", ref: this._getMainRef, children: children }); return div; //} } componentDidMount() { for (const p of Object.keys(this.props)) { if (isValidProp(this, p)) { { this[p] = this.props[p]; } } } this._elRef.style.width = this._width ? this._width : ""; this._elRef.style.height = this._height ? this._height : ""; this._elRef.appendChild(this.container.getNativeElement()); this._chart.notifyResized(); this.initializeContent(); } shouldComponentUpdate(nextProps, nextState) { const mod = getModifiedProps(this.props, nextProps); for (const p of Object.keys(mod)) { if (isValidProp(this, p)) { this[p] = mod[p]; } } return true; } _getMainRef(div) { this._elRef = div; } constructor(props) { super(props); this._contentChildren = null; this._contentChildrenActual = null; this._contentChildrenMap = null; this._contentChildrenUnresolved = 0; /** * The series actually present in the chart. Do not directly modify this array. * This array's contents can be modified by causing Angular to reproject the child content. * Or adding and removing series from the manual series collection on the series property. */ this.actualSeries = []; this._seriesAdapter = null; this._series = null; this._defaultTooltips = null; this._uniqueTooltipId = 0; this.__p = null; this._hasUserValues = new Set(); this._stylingContainer = null; this._stylingParent = null; this._inStyling = false; this._sliceClick = null; this._sliceClick_wrapped = null; this._holeDimensionsChanged = null; this._holeDimensionsChanged_wrapped = null; if (this._styling) { NamePatcher.ensureStylablePatched(Object.getPrototypeOf(this)); } this._getMainRef = this._getMainRef.bind(this); this._tooltipRef = this._tooltipRef.bind(this); this._activeTooltipElements = new Map(); this._activeTooltips = new Map(); this._currentTooltips = []; this._contentChildrenManager = new ContentChildrenManager((ch) => ch.key || ch.props.name, (ch) => ch.key || ch.props.name, () => this._updateContentChildren()); let container = null; if (document) { container = document.createElement("div"); container.style.display = "block"; container.style.width = "100%"; container.style.height = "100%"; } var root; root = container; this._renderer = new ReactRenderer(root, document, true, DataChartStylingDefaults); this.container = this._renderer.getWrapper(container); this._implementation = this.createImplementation(); this._implementation.externalObject = this; this.onImplementationCreated(); this._wrapper = this._renderer; var chart = this.i; this._chart = chart; chart.provideContainer(this._renderer); this._renderer.addSizeWatcher(() => { this._chart.notifyResized(); }); } destroy() { this._chart.destroy(); this._wrapper.destroy(); } initializeContent() { this._updateContentChildren(); this._seriesAdapter = new CollectionAdapter(this.contentSeries, this.i.series, this.actualSeries, (c) => c.i, (i) => { i.owner = this; // (<any>i)._provideRenderer(this._dataSource); if (this.container && this.container.getNativeElement().parentElement) { i._styling(this.container.getNativeElement(), this, this); } this._ensureDefaultTooltip(i); this._ensureTooltipCreated(i); //(<any>i)._provideRenderer(this._wrapper); }, (i) => { //(<any>i)._provideRenderer(null); }); this._styling(this.container.getNativeElement(), this); if (this.actualSeries && this.actualSeries.length > 0) { var currSeries = this.actualSeries; for (var i = 0; i < currSeries.length; i++) { currSeries[i]._styling(this.container.getNativeElement(), this, this); } } this.i.notifyResized(); } componentWillUnmount() { } onImplementationCreated() { } createImplementation() { return new XamDoughnutChart(); } get i() { return this._implementation; } createSeriesComponent(type) { if (TypeRegistrar.isRegistered(type)) { let s = TypeRegistrar.create(type); s.owner = this; s._provideRenderer(this._wrapper); return s; } else { //we shouldn't get here, hopefully. throw Error("series type not loaded: " + type); } } /** * A collection or manually added series for the chart. */ get series() { if (this._series === null) { let coll = new IgrRingSeriesCollection(); let inner = coll._innerColl; inner.addListener((sender, e) => { switch (e.action) { case NotifyCollectionChangedAction.Add: this._seriesAdapter.addManualItem(e.newItems.item(0)); break; case NotifyCollectionChangedAction.Remove: this._seriesAdapter.removeManualItemAt(e.oldStartingIndex); break; case NotifyCollectionChangedAction.Replace: this._seriesAdapter.removeManualItemAt(e.oldStartingIndex); this._seriesAdapter.insertManualItem(e.newStartingIndex, e.newItems.item(0)); break; case NotifyCollectionChangedAction.Reset: this._seriesAdapter.clearManualItems(); break; } }); this._series = coll; } return this._series; } _ensureTooltipCreated(series) { if (series.i.toolTip == null) { let tooltip = this.createTooltip(); let ele = tooltip; if (tooltip == null) { return; } if (typeof (series.tooltipTemplate) == "function") { ele.tooltipTemplate = series.tooltipTemplate; } series._tooltipContent = tooltip; //(<any>tooltip.instance).template = this._tooltipTemplate; series.i.toolTip = this.createWrapper(tooltip); if (this._activeTooltips.has(ele)) { let tCont = this._activeTooltips.get(ele); tCont.template = ele.tooltipTemplate; } } } createWrapper(ele) { let wrapper = new ReactWrapper(ele, this._wrapper); wrapper.updateToolTip = ele.updateToolTip; wrapper.hideToolTip = ele.hideToolTip; if (this._activeTooltips.has(ele)) { let tCont = this._activeTooltips.get(ele); tCont.template = ele.tooltipTemplate; } return wrapper; } _ensureDefaultTooltip(series) { if (this._defaultTooltips == null) { return; } this._defaultTooltips.instance["ensureDefaultTooltip"](series); } _onDefaultTooltipsReady(cr) { if (this.actualSeries && this.actualSeries.length > 0) { var currSeries = this.actualSeries; for (var i = 0; i < currSeries.length; i++) { if (currSeries[i].showDefaultTooltip) { this._ensureDefaultTooltip(currSeries[i]); } } } } createTooltip() { let wrapper = this._wrapper.createElement("div"); let ele = wrapper.getNativeElement(); ele.key = "__tooltip_" + this._uniqueTooltipId; this._uniqueTooltipId++; this._currentTooltips = this._currentTooltips.slice(0); this._currentTooltips.push(ele); //let element = React.createElement(Tooltip, ); //let portal = ReactDOM.createPortal(element, ele); let self = this; ele.updateToolTip = function (c, isSubContent) { if (c.externalObject) { c = c.externalObject; } else { let ext = new IgrDataContext(); ext._implementation = c; c = ext; } if (!isSubContent) { if (ele.parentElement != self.container.getNativeElement()) { if (ele.parentElement != null) { ele.parentElement.removeChild(ele); } self.container.getNativeElement().appendChild(ele); } } else { c.isSubContent = true; } if (self._activeTooltips.has(ele)) { let t = self._activeTooltips.get(ele); if (t.template === null && ele.tooltipTemplate !== null) { t.template = ele.tooltipTemplate; } t.dataContext = c; } ele.style.display = "block"; return true; }; ele.hideToolTip = function () { ele.style.display = "none"; }; ele.style.display = "none"; this._updateTooltipState(); return ele; } _updateTooltipState() { this.setState({ tooltips: this._currentTooltips }); } /** * Gets or sets whether the slices can be selected. */ get allowSliceSelection() { return this.i.aw; } set allowSliceSelection(v) { this.i.aw = ensureBool(v); } /** * Gets or sets whether all surface interactions with the plot area should be disabled. */ get isSurfaceInteractionDisabled() { return this.i.ax; } set isSurfaceInteractionDisabled(v) { this.i.ax = ensureBool(v); } /** * Gets or sets whether the slices can be exploded. */ get allowSliceExplosion() { return this.i.av; } set allowSliceExplosion(v) { this.i.av = ensureBool(v); } /** * Gets or sets the inner extent of the doughnut chart. It is percent from the outer ring's radius. */ get innerExtent() { return this.i.a3; } set innerExtent(v) { this.i.a3 = +v; } /** * Gets or sets the fill brush. */ get selectedSliceFill() { return this.i.cg ? this.i.cg.fill : null; } set selectedSliceFill(v) { this.ensureSelectedStyle(); this.i.cg.fill = v; } /** * Gets or sets the stroke brush. */ get selectedSliceStroke() { return this.i.cg ? this.i.cg.stroke : null; } set selectedSliceStroke(v) { this.ensureSelectedStyle(); this.i.cg.stroke = v; } /** * Gets or sets the stroke thickness. */ get selectedSliceStrokeThickness() { return this.i.cg ? this.i.cg.strokeThickness : NaN; } set selectedSliceStrokeThickness(v) { this.ensureSelectedStyle(); this.i.cg.strokeThickness = +v; } /** * Gets or sets the opacity. */ get selectedSliceOpacity() { return this.i.cg ? this.i.cg.opacity : NaN; } set selectedSliceOpacity(v) { this.ensureSelectedStyle(); this.i.cg.opacity = +v; } ensureSelectedStyle() { if (this.i.cg) { return; } this.i.cg = new Style(); } /** * Gets or sets the scaling value used to affect the pixel density of the control. * A higher scaling ratio will produce crisper visuals at the expense of memory. Lower values will cause the control * to appear blurry. */ get pixelScalingRatio() { return this.i.a4; } set pixelScalingRatio(v) { this.i.a4 = +v; } /** * Resolved pixel scaling ratio. Unless explicitly overridden by the * IgxDoughnutChart.PixelScalingRatioComponent property, * this one returns the default ratio enforced by device. High resolution devices will initialize this property * to a higher value. */ get actualPixelScalingRatio() { return this.i.a1; } set actualPixelScalingRatio(v) { this.i.a1 = +v; } findByName(name) { if (this.findEphemera) { if (name && name.indexOf("@@e:") == 0) { return this.findEphemera(name); } } if (this.series != null && this.series.findByName && this.series.findByName(name)) { return this.series.findByName(name); } return null; } get hasUserValues() { return this._hasUserValues; } __m(propertyName) { if (!this._inStyling) { this._hasUserValues.add(propertyName); } } _styling(container, component, parent) { if (this._inStyling) { return; } this._inStyling = true; this._stylingContainer = container; this._stylingParent = component; let genericPrefix = ""; let typeName = this.i.$type.name; if (typeName.indexOf("Xam") === 0) { typeName = typeName.substring(3); } genericPrefix = toSpinal("DoughnutChart"); let additionalPrefixes = []; let prefix = toSpinal(typeName); additionalPrefixes.push(prefix + "-"); let b = this.i.$type.baseType; while (b && b.name != "Object" && b.name != "Base" && b.name != "Control" && b.Name != "DependencyObject" && b.Name != "FrameworkElement") { typeName = b.name; if (typeName.indexOf("Xam") === 0) { typeName = typeName.substring(3); } let basePrefix = toSpinal(typeName); additionalPrefixes.push(basePrefix + "-"); b = b.baseType; } if (parent) { let parentTypeName = parent.i.$type.name; if (parentTypeName.indexOf("Xam") === 0) { parentTypeName = parentTypeName.substring(3); } let parentPrefix = toSpinal(parentTypeName); additionalPrefixes.push(parentPrefix + "-" + genericPrefix + "-"); additionalPrefixes.push(parentPrefix + "-" + prefix + "-"); } initializePropertiesFromCss(container, this, genericPrefix + "-", this.hasUserValues, false, additionalPrefixes); if (this._otherStyling) { this._otherStyling(container, component, parent); } this._inStyling = false; } /** * Called by the UI framework to provide a UI container for rendering this control. * @param container * The UI container element. */ provideContainer(container) { this.i.provideContainer(container); } /** * Called when the control has been resized. */ notifyResized() { this.i.notifyResized(); } /** * Gets the ID of the UI container. */ getContainerID() { let iv = this.i.bd(); return (iv); } /** * Gets the center coordinates of the doughnut chart's center presenter. */ getCenterCoordinates() { let iv = this.i.cf(); return fromPoint(iv); } /** * Gets the hole radius of the doughnut chart's center presenter. */ getHoleRadius() { let iv = this.i.a2(); return (iv); } /** * Use to force the doughnut chart to finish any deferred work before printing or evaluating its visual. * This should only be called if the visual of the doughnut chart needs to be synchronously saved or evaluated. * Calling this method too often will hinder the performance of the doughnut chart. */ flush() { this.i.bk(); } /** * Returns the chart visuals expressed as a serialized string. */ exportSerializedVisualData() { let iv = this.i.bc(); return (iv); } notifyInsertItem(source_, index, newItem) { this.i.bm(source_, index, newItem); } notifySetItem(source_, index, oldItem, newItem) { this.i.bp(source_, index, oldItem, newItem); } /** * Used to manually notify the chart that the data source has reset or cleared its items. */ notifyClearItems(source_) { this.i.bl(source_); } notifyRemoveItem(source_, index, oldItem) { this.i.bn(source_, index, oldItem); } /** * Raised when the slice is clicked. */ get sliceClick() { return this._sliceClick; } set sliceClick(ev) { if (this._sliceClick_wrapped !== null) { this.i.sliceClick = delegateRemove(this.i.sliceClick, this._sliceClick_wrapped); this._sliceClick_wrapped = null; this._sliceClick = null; } this._sliceClick = ev; this._sliceClick_wrapped = (o, e) => { let outerArgs = new IgrSliceClickEventArgs(); outerArgs._provideImplementation(e); if (this.beforeSliceClick) { this.beforeSliceClick(this, outerArgs); } if (this._sliceClick) { this._sliceClick(this, outerArgs); } }; this.i.sliceClick = delegateCombine(this.i.sliceClick, this._sliceClick_wrapped); ; } /** * Raised when the dimensions (center point or radius) of the doughnut hole change. */ get holeDimensionsChanged() { return this._holeDimensionsChanged; } set holeDimensionsChanged(ev) { if (this._holeDimensionsChanged_wrapped !== null) { this.i.holeDimensionsChanged = delegateRemove(this.i.holeDimensionsChanged, this._holeDimensionsChanged_wrapped); this._holeDimensionsChanged_wrapped = null; this._holeDimensionsChanged = null; } this._holeDimensionsChanged = ev; this._holeDimensionsChanged_wrapped = (o, e) => { let outerArgs = new IgrHoleDimensionsChangedEventArgs(); outerArgs._provideImplementation(e); if (this.beforeHoleDimensionsChanged) { this.beforeHoleDimensionsChanged(this, outerArgs); } if (this._holeDimensionsChanged) { this._holeDimensionsChanged(this, outerArgs); } }; this.i.holeDimensionsChanged = delegateCombine(this.i.holeDimensionsChanged, this._holeDimensionsChanged_wrapped); ; } }