Chart became stacked when x axis set to date-time format - anychart

I have angular chart component which draws charts for time series data. The code is here:
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnDestroy,
ViewChild,
ViewEncapsulation,
} from '#angular/core';
import { notNullFilter } from '#app/lib/rxjs-not-null-filter';
import { AnychartCartesian } from '#app/shared/anychart/anychart.module';
import { ResizeObserver } from 'resize-observer';
import { combineLatest, BehaviorSubject, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
/**
* Компонент для построения графиков с использованием anychart.
*/
#Component({
selector: 'app-anychart',
templateUrl: './anychart.component.html',
styleUrls: ['./anychart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class AppAnychartComponent implements OnDestroy {
constructor() {
combineLatest([
this.elementRef$,
this.anychartCartesian$,
this.height$.pipe(notNullFilter()),
])
.pipe(
takeUntil(this.destroy$),
)
.subscribe(([ref, chart, height]) => {
this.destroyPrevAnychart();
if (ref !== null && chart !== null) {
const el = ref.nativeElement;
this.resizeObserver.observe(el);
chart.height(height);
chart.xScale('date-time'); // it makes chart stacked
chart.credits().enabled(false);
chart.container(el);
chart.draw();
this.prevChart = chart;
}
});
}
private readonly resizeObserver = new ResizeObserver(entries => {
const chart = this.anychartCartesian$.getValue();
if (chart === null) { return; }
const widthPx: number = entries.reduce((acc, entry) => entry.contentRect.width || acc, 0);
console.log('TODO: resize chart:', widthPx); // TODO: resize chart
});
private readonly elementRef$ = new BehaviorSubject<ElementRef<HTMLDivElement> | null>(null);
#ViewChild('elRef') set elementRef(ref: ElementRef<HTMLDivElement> | undefined) {
this.elementRef$.next(ref || null);
}
private readonly anychartCartesian$ = new BehaviorSubject<AnychartCartesian | null>(null);
#Input() set chart(chart: AnychartCartesian) {
this.anychartCartesian$.next(chart);
}
public readonly height$ = new BehaviorSubject<string | null>(null);
#Input() set height(height: string) {
this.height$.next(height);
}
private prevChart: AnychartCartesian | null = null;
private readonly destroy$ = new Subject<void>();
/**
* #inheritDoc
*/
public ngOnDestroy(): void {
this.resizeObserver.disconnect();
this.destroy$.next();
this.destroy$.complete();
this.destroyPrevAnychart();
}
private destroyPrevAnychart(): void {
if (this.prevChart !== null) {
this.prevChart.dispose();
this.prevChart = null;
// ensure clean up previous svg
const ref = this.elementRef$.getValue();
if (ref !== null) {
const el = ref.nativeElement;
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
}
}
}
If I comment line chart.xScale('date-time'); chart looks like this
If I uncomment this line, chart becomes like this
I've read docs but I have no idea why this happens. The question is how with date-time format of the x scale make normal bar chart as in the first screenshot? Thanks in advance!
UPD1. When I changed code from
const series1 = chart.colum(mapping1);
to
const series1 = chart.line(mapping1);`
as was proposed in answer, I've got chart which looks like this:

This is expected behavior and it's not stacked mode. Let me explain. Every series has a point for the 1st March (for example 2020-03-01). If the scale is Ordinal (1st screenshot) it creates a category named "1 Mar".
If the scale is dateTime, it becomes linear (not ordinal, the 2nd screenshot). In this case, every bar is placed strictly according to it's timestamp. But all bars have the same dateTime "2020-03-01". So, as a result, all they are placed in the same position. And looks like stacked, but it's not. The height of the bar is not a sum of all series values in this X coordinate.
There are several available options to avoid it:
Use the Ordinal scale which is the default
Use dateTime scale, but switch column series to line or spline, it will increase readability
Use dateTime scale, but create these series on separate charts.

Related

How to pass an array from one component to its sub component

After googling lots of related answers and tries, I seemly have to seek help here.
I have an angular application, one of its components named stock-subscribe, which is used to display the feeTypes that a customer subscribed. Within this component, I created another component, stock-addsubs, used to display the feeTypes that are not yet subscribed by this customer.
enter image description here
Obviously, the two feeType lists can compose one whole list. From stock-subscribe, I can get an array, subscribedFeeIds, which holds all the ids of those subscribed feeTypes.
My requirement is to pass this array, subscribedFeeIds, to the stock-addsubs component so that I can filter out those yet unsubscribed feeTypes based on those ids of the array.
To my best understanding, the data passing from one component to its sub component should be a simple process and neither of two component html templates should be involved for my case. Of the many googled solutions, using #Output and #Input seems the simpler than event emitting. However, none can successfully pass the elements of the array in the sub component.
I can get the expected id list (subscribedFeeIds[]) from the stock-subscribe component, and all the rest code work fine so long as the array passed to the sub component is not EMPTY.
1) stock-subscribe.component.ts
#Component({
selector: 'app-stock-subscribe',
templateUrl: './stock-subscribe.component.html',
styleUrls: ['./stock-subscribe.component.scss']
})
export class StockSubscribeComponent implements OnInit {
userSubscribe: ISubscribe;
#Output() subscribedFeeIds: any = [];
listData: MatTableDataSource<any>;
constructor(private accountService: AccountService,
private stockService: StockService) { }
ngOnInit(): void {
this.createSubsList();
}
createSubsList() {
this.accountService.getUserAccount()
.subscribe(account => {
let userId = account.id.toString();
this.stockService.getUserSubscribes(userId).subscribe((data: ISubscribe[]) => {
// get the id of subscribed fee type and thenpublish to other component
for (var i = 0; i < data.length; i++)
{
if (data[i].status)
this.subscribedFeeIds.push(data[i].fees[0].id);
}
console.log(this.subscribedFeeIds);
// prepare the list source for display
this.listData = new MatTableDataSource(data);
this.listData.sort = this.sort;
}, error => {
console.log(error);
});
}, error => {
console.log(error);
});
}
}
2) stock-addsubs.component.ts
#Component({
selector: 'app-stock-addsubs',
templateUrl: './stock-addsubs.component.html',
styleUrls: ['./stock-addsubs.component.scss']
})
export class StockAddsubsComponent implements OnInit {
listData: MatTableDataSource<any>;
#Input() subscribedFeeIds: any [];
constructor(private feesService: FeesService) { }
ngOnInit(): void {
this.createFeeList();
}
createFeeList() {
this.feesService.getFeeList().subscribe((data: IFee[]) => {
// filter those already subscribed
for (var i = 0; i < this.subscribedFeeIds.length; i++)
{
for (var j = 0; j < data.length; j++)
{
data = data.filter(f => f.id != this.subscribedFeeIds[i]);
}
}
// prepare data to display
this.listData = new MatTableDataSource(data);
}, error => {
console.log(error);
});
}
}
You can implement either one of these methods:
1.) Enhance your #Input and #Output based on your code above:
stock-subscribe.component.ts
#Component({...})
export class StockSubscribeComponent implements OnInit {
#Output() subscribedFeeIds: EventEmitter<any> = new EventEmitter<any>();
list: any[] = []; // To store your ids, do not use the #Output variable above to push things inside a loop, you may not want to continuously emit data for an nth number of the sequence.
...
createSubsList() {
...
for (var i = 0; i < data.length; i++) {
...
if (data[i].status)
this.list.push(data[i].fees[0].id); // Push IDs on this.list not on this.subscribedFeeIds
}
this.subscribedFeeIds.emit(this.list); // After the loop finishes, emit the final list value
// Do not use .push() on #Output variable, this is an emission variable so use .emit()
}
}
or you could also do any of these methods when handling your array list:
// METHOD #1
this.filteredIds = [];
for(const { status, fees } of FEES_DATA) {
if (status) filteredIds.push(fees[0].id);
}
OR
// METHOD #2
this.filteredIds = FEES_DATA
.filter(({ status }) => status)
.map(({ fees }) => fees[0].id);
// Then emit the filtered ids
this.list.emit(this.filteredIds);
stock-addsubs.component.ts
#Component({...})
export class StockAddsubsComponent implements OnInit {
// Since you are waiting for emission from StockSubscribeComponent which the
// emission also waits till the loop is finished based on the code above, better to
// implement the #Input like this so as to avoid processing a an empty list
#Input() set subscribedFeeIds(list: any[]) {
if (list && list.length) { // If the list now has items, call createFeeList() function
this.createFeeList();
}
}
...
}
____
or
#Component({...})
export class StockAddsubsComponent implements OnInit, OnChanges { // Add OnChanges
#Input() subscribedFeeIds: any [];
ngOnChanges({ subscribedFeeIds }: SimpleChanges) { // SimpleChanges from #angular/core
if (subscribedFeeIds && subscribedFeeIds.currentValue && subscribedFeeIds.currentValue.length) {
this.createFeeList();
}
// You can also console the subscribedFeeIds and check it's value or changes
console.log(subscribedFeeIds);
}
}
Have created a Stackblitz Demo for your reference
2.) Use RxJS Subject or BehaviorSubject to emit data
Use this Stackblitz Demo for your reference.

Autocomplete off on Angular Directive for Date Picker

I have a directive for JQuery Date picker which injects date picker into input HTML control. This was developed by a previous developer and I am pretty new to Angular at this moment.
My question is that is there any way to prevent showing auto complete on all the date pickers that we inject via this directive?
export class DanialDatePickerDirective implements ControlValueAccessor {
constructor(protected el: ElementRef, private renderer: Renderer) { }
#Input() dateformat: string = "DD-MMM-YY";
#Input() ngModel: any;
#Input() setDefaultDate: boolean;
onModelChange: Function = () => { };
onModelTouched: Function = () => { };
writeValue(value: any) {
if (value) {
var ff = new Date(value);
$(this.el.nativeElement).datepicker("setDate", ff);
}
else {
$(this.el.nativeElement).datepicker("setDate", "");
}
}
registerOnChange(fn: Function): void {
this.onModelChange = fn;
}
registerOnTouched(fn: Function): void {
this.onModelTouched = fn;
}
onBlur() {
this.onModelTouched();
}
ngAfterViewInit() {
var self = this;
$(this.el.nativeElement).datepicker({
dateFormat: 'dd-M-y',
changeMonth: true,
changeYear: true,
showOtherMonths: true,
selectOtherMonths: true
});
if (this.setDefaultDate) {
var ff = new Date(self.ngModel);
setTimeout(function () {
$(self.el.nativeElement).datepicker("setDate", ff);
}, 200);
}
$(this.el.nativeElement).on('change', (e: any) => {
var model = e.target.value;
var date = null;
var monthstring = '';
if (model.indexOf("-") > 0){
monthstring = model.substring(model.indexOf("-") + 1, 5);
}
if (isNaN(parseInt(monthstring))) {
var tt = moment(model, "DD-MMM-YY").format('YYYY-MM-DD');
date = tt;
model = moment(model, "DD-MMM-YYYY").format('MM-DD-YYYY')
}
else {
date = moment(model, "DD-MM-YYYY").format('YYYY-MM-DD');
model = moment(model, "DD-MM-YYYY").format('MM-DD-YYYY')
}
$(".ui-datepicker a").removeAttr("href");
self.onModelChange(date);
self.writeValue(date.toString());
});
}
}
The only approach who works for me:
First, make sure to set autocomplete="off" on both, the input element itself and the parent form.
Second, make sure to assign an unique name to your input field always.
This can be achieved by simply generating a random number and using this number in the name of the field.
private getUniqueName() {
return Math.floor(Math.random() * Date.now());
}
Explanation:
In the past, many developers would add autocomplete="off" to their
form fields to prevent the browser from performing any kind of
autocomplete functionality. While Chrome will still respect this tag
for autocomplete data, it will not respect it for autofill data.
https://developers.google.com/web/updates/2015/06/checkout-faster-with-autofill.
So autocomplete="off" solves the autocomplete issue. But to solve the autofill you need to play dirty with the browser by changing the name of the input over an over again, that way the browser will never know how to autofill ;)

How do I create an ag-Grid cell editor using React and TypeScript?

I see that the ag-grid-react repo has types, and I also see that the ag-grid-react-example repo has examples. But how do I put the two together and create a cell editor with React and Types?
I'm guessing it's something like this but I can't make TypeScript happy:
class MyCellEditor implements ICellEditorReactComp {
public getValue() {
// return something
}
public render() {
const { value } = this.props
// return something rendering value
}
}
I implemented ICellEditor and used ICellEditorParams for prop definitions. For example, this MyCellEditor example from their documentation:
// function to act as a class
function MyCellEditor () {}
// gets called once before the renderer is used
MyCellEditor.prototype.init = function(params) {
// create the cell
this.eInput = document.createElement('input');
this.eInput.value = params.value;
};
// gets called once when grid ready to insert the element
MyCellEditor.prototype.getGui = function() {
return this.eInput;
};
// focus and select can be done after the gui is attached
MyCellEditor.prototype.afterGuiAttached = function() {
this.eInput.focus();
this.eInput.select();
};
// returns the new value after editing
MyCellEditor.prototype.getValue = function() {
return this.eInput.value;
};
// any cleanup we need to be done here
MyCellEditor.prototype.destroy = function() {
// but this example is simple, no cleanup, we could
// even leave this method out as it's optional
};
// if true, then this editor will appear in a popup
MyCellEditor.prototype.isPopup = function() {
// and we could leave this method out also, false is the default
return false;
};
became this:
class MyCellEditor extends Component<ICellEditorParams,MyCellEditorState> implements ICellEditor {
constructor(props: ICellEditorParams) {
super(props);
this.state = {
value: this.props.eGridCell.innerText
};
}
// returns the new value after editing
getValue() {
// Ag-Grid will display this array as a string, with elements separated by commas, by default
return this.state.value;
};
// Not sure how to do afterGuiAttached()
// if true, then this editor will appear in a popup
isPopup() {
return true;
};
render() {
return (
<div>
hello
</div>
);
}
}

Context issues with React and requestAnimationFrame

I'm currently playing around with React and Three.js. I'm trying to call an update function that is passed as a prop to another component as below. The problem is I get an error sayign this.cube is undefined.
public renderer : THREE.WebGLRenderer;
public scene : THREE.Scene = new THREE.Scene();
public camera : THREE.PerspectiveCamera = new THREE.PerspectiveCamera(70, 400 / 300, 0.1, 0.1);
public cube : THREE.Mesh;
public componentDidMount() {
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
this.cube = new THREE.Mesh(geometry, material);
this.scene.add(this.cube);
}
public update = () => {
console.log(this);
console.log(this.cube);
this.cube.rotation.x += 0.01;
this.cube.rotation.y += 0.01;
}
public render() {
const sceneArr : THREE.Scene[] = [];
sceneArr.push(this.scene);
return (
<React.Fragment>
<Three mainCamera={this.camera} width={400} height={300} update={this.update} scenes={sceneArr} />
</React.Fragment>
);
}
Here is the render function inside the `Three` component calling `requestAnimationFrame`.
public threeRender = () => {
this.props.update();
requestAnimationFrame(this.threeRender);
this.props.scenes.forEach(scene => {
this.renderer.render(scene, this.props.mainCamera);
});
}
I assumed that the context of this inside update was incorrectly referring to the Three component, but it turns out that the print statements inside update showed contradicting evidence.
console.log(this) returns the correct object context, and the cube member shows up in the log, but console.log(this.cube) returns undefined. This makes no sense to me. Any ideas?
Inside of your constructor initialize cube and then you should be able to refer to it without getting that error.
constructor(props){
this.cube = {}
}
Now you can give this.cube a value and refer to it from within update()

Need to integrate mxGraph with react js

Is there any sample or example projects that explain how to integrate mxGraph with react js?
import React, {Component} from "react";
import PropTypes from "prop-types";
import ReactDOM from "react-dom";
import {
mxGraph,
mxParallelEdgeLayout,
mxConstants,
mxEdgeStyle,
mxLayoutManager,
mxCell,
mxGeometry,
mxRubberband,
mxDragSource,
mxKeyHandler,
mxCodec,
mxClient,
mxConnectionHandler,
mxUtils,
mxToolbar,
mxEvent,
mxImage,
mxFastOrganicLayout
} from "mxgraph-js";
class mxGraphGridAreaEditor extends Component {
constructor(props) {
super(props);
this.state = {
};
this.LoadGraph = this
.LoadGraph
.bind(this);
}
LoadGraph(data) {
var container = ReactDOM.findDOMNode(this.refs.divGraph);
var zoomPanel = ReactDOM.findDOMNode(this.refs.divZoom);
// Checks if the browser is supported
if (!mxClient.isBrowserSupported()) {
// Displays an error message if the browser is not supported.
mxUtils.error("Browser is not supported!", 200, false);
} else {
// Disables the built-in context menu
mxEvent.disableContextMenu(container);
// Creates the graph inside the given container
var graph = new mxGraph(container);
// Enables rubberband selection
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This is normally the first
// child of the root (ie. layer 0).
var parent = graph.getDefaultParent();
// Enables tooltips, new connections and panning
graph.setPanning(true);
//graph.setTooltips(true); graph.setConnectable(true);
graph.setEnabled(false);
// Autosize labels on insert where autosize=1
graph.autoSizeCellsOnAdd = true;
// Allows moving of relative cells
graph.isCellLocked = function (cell) {
return this.isCellsLocked();
};
graph.isCellResizable = function (cell) {
var geo = this
.model
.getGeometry(cell);
return geo == null || !geo.relative;
};
// Truncates the label to the size of the vertex
graph.getLabel = function (cell) {
var label = this.labelsVisible
? this.convertValueToString(cell)
: "";
var geometry = this
.model
.getGeometry(cell);
if (!this.model.isCollapsed(cell) && geometry != null && (geometry.offset == null || (geometry.offset.x == 0 && geometry.offset.y == 0)) && this.model.isVertex(cell) && geometry.width >= 2) {
var style = this.getCellStyle(cell);
var fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE;
var max = geometry.width / (fontSize * 0.625);
if (max < label.length) {
return label.substring(0, max) + "...";
}
}
return label;
};
// Enables wrapping for vertex labels
graph.isWrapping = function (cell) {
return this
.model
.isCollapsed(cell);
};
// Enables clipping of vertex labels if no offset is defined
graph.isLabelClipped = function (cell) {
var geometry = this
.model
.getGeometry(cell);
return (geometry != null && !geometry.relative && (geometry.offset == null || (geometry.offset.x == 0 && geometry.offset.y == 0)));
};
var layout = new mxParallelEdgeLayout(graph);
// Moves stuff wider apart than usual
layout.forceConstant = 140;
//// Adds cells to the model in a single step
graph
.getModel()
.beginUpdate();
try {
//mxGrapg componnent
var doc = mxUtils.createXmlDocument();
var node = doc.createElement('YES')
node.setAttribute('ComponentID', '[P01]');
var vx = graph.insertVertex(graph.getDefaultParent(), null, node, 240, 40, 150, 30);
var v1 = graph.insertVertex(parent, null, 'Example_Shape_01', 20, 120, 80, 30);
var v2 = graph.insertVertex(parent, null, 'Example_Shape_02', 300, 120, 80, 30);
var v3 = graph.insertVertex(parent, null, 'Example_Shape_03', 620, 180, 80, 30);
var e1 = graph.insertEdge(parent, null, 'Example Edge name_01', v1, v2);
var e2 = graph.insertEdge(parent, null, 'Example Edge name_02', v2, v3);
var e3 = graph.insertEdge(parent, null, 'Example Edge name_02', v1, v3);
// Gets the default parent for inserting new cells. This is normally the first
// child of the root (ie. layer 0).
var parent = graph.getDefaultParent();
// Executes the layout
layout.execute(parent);
//data
} finally {
// Updates the display
graph
.getModel()
.endUpdate();
}
// Automatically handle parallel edges
var layout = new mxParallelEdgeLayout(graph);
var layoutMgr = new mxLayoutManager(graph);
// Enables rubberband (marquee) selection and a handler for basic keystrokes
var rubberband = new mxRubberband(graph);
var keyHandler = new mxKeyHandler(graph);
}
}
render() {
return (
<div className="graph-container" ref="divGraph" id="divGraph"/>
);
}
}
export default mxGraphGridAreaEditor;
NOTE: This code doesn't work properly when you run here. It's a sample guidelines to integrate mxgraph with react js
Here you go:
run:
npm i --save-dev script-loader
npm i mxgraph
on your code:
/* eslint import/no-webpack-loader-syntax: off */
/* eslint no-undef: off */
import mxClient from "script-loader!mxgraph/javascript/mxClient";
and you will have access to all its objects:
export default class DG {
static drawGraph(vertexes){
let container = document.createElement("div")
// Disables the built-in context menu
mxEvent.disableContextMenu(container);
// Creates the graph inside the given container
var graph = new mxGraph(container);;
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
var parent = graph.getDefaultParent();
var selectionCells= [];
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
...
...
If you don't want to use the built-in stylesheets and resources add to your index.html file:
<script type="text/javascript"> var mxLoadResources = false; var mxLoadStylesheets = false;</script>
There is a grunt file in the mxGraph repo which can be useful for you.
First commit message of this file:
Make objects available via CommonJS require, and create a package.json file for npm
For the usage there is the Anchors example (file)
Please share your experience about the integration with us.
The following on codesandbox might be of some help to you:

Resources