ChartKick - ChartJs - Legend onClick override with default behaviour - javascript

In my VueJs application, I present Pie-Chart & table showing data. On legend onClick I am trying to filter table rows.
Here is my code
<pie-chart
:donut="false"
:data="charInfo"
:responsive="true"
:library="{legend:{display: true,onClick:itemSelected}}"
legend="top"
:colors="['#5C9AFF', '#FF3263']"/>
Here clicking on particular legend, 'itemSelected' get called with 2 arguments, 1st is mouseEvent & 2nd is legendItem. I can get the legend text & can filter table rows. But the problem is, it override default behaviour & striking-out legend & hide/show section of pie chart disabled. While searching, I came across
ChartJs Legend onClick Issue. It says to store original legend onClick event & call that from my code. I can store original onClick using something like 'const original = Chart.defaults.pie.legend.onClick', but problem is I dont have chart object to call 'original.call(,event,legendItem). And here I am sort of stuck.

I went round and round trying a range of different implementations to solve this very problem for myself while using ng2-charts (incidentally it is how i found this question). I ended up adding an #ViewChild to reference my chart that was defined in the html.
references needed in the ts for this workaround:
import { Component, Input, Host, OnInit, OnDestroy, ViewChild, AfterViewInit } from '#angular/core';
import { ChartsModule, BaseChartDirective } from 'ng2-charts';
added to the top of my class in the ts:
export class StatisticsComponent implements OnInit, OnDestroy {
#ViewChild('myChart') myChart : BaseChartDirective;
the html object for the chart using ng2-charts:
<canvas *ngIf = "loaded"
baseChart
#myChart = 'base-chart'
[datasets]="chartData"
[colors]="chartColors"
[chartType]="'line'"
[labels]="chartLabels"
[options]="chartOptions"
[legend]="true"
(chartClick)="onChartClick($event)">
</canvas>
In the method you then use for overriding the legend onClick (I personally did it in the chartOptions) you can add this line of code:
this.chart.data.datasets[legendItem.datasetIndex].hidden = !this.chart.data.datasets[legendItem.datasetIndex].hidden;
this.chart.update();
This replicates the standard "onClick" of the legend Item so you dont need to bother storing the original function. Probably important to mention that in this code the legendItem is the second parameter from the onClick (with the first being the event):
function(e: any, legendItem: any)
The only issue with this fix is that by default, in ng2-charts the .hidden artibute is not exposed by default so i had to do a little hack in the module following this method:
https://github.com/valor-software/ng2-charts/issues/915.
While i appreciate your question does not relate to ng2-charts. I imagine that you could likely use a similar method in your wrapper. At the very least I figured that after I managed to sort my problem that I would share with you what I did :)

Related

How to use d3 component in react without breaking its animation

I was trying out d3 sunburst component from here. I wanted to use it in react. To use it in react, we have to use another library called react-kapsule:
import SunburstChart from "sunburst-chart";
import fromKapsule from "react-kapsule";
const ReactSunburst = fromKapsule(SunburstChart, {
methodNames: ["onClick"]
});
<ReactSunburst
width={200}
height={200}
label="name"
size="size"
data={flare}
onClick={(entry) => {
console.log("Hello from inside onClick handler!!");
}}
/>
It renders as follows:
The problem is with specifying custom onClick handler. When I specify methodNames: ["onClick"], clicking on slice of sunburst chart zooms in that slice, but it does not log the message to the console. codesandbox link
If I remove onClick from methodNames, it logs the message to method names, but it does not zooms in the slice. codesandbox link
How I add working onClick handler while at the same time ensuring that the component's default behavior wont break?

Angular component creation best practices

I'm often in doubt when I want a new behaviour of a component.
Let's make a simple example, I have <app-title> component:
<div>
<h1>{{title}}</h1>
</div>
Some time after, inside another page I need to put a button alongside the title. The problem is, should I create a new title component or should I parametrize the existing one?
I can edit <app-title> to look like this:
export class AppTitleComponent implements OnInit {
#Input() showButton: boolean;
title = 'App title';
constructor() {}
ngOnInit() {}
}
<div>
<h1>{{title}}</h1>
<button *ngIf="showButton">{{buttonTitle}}</button>
</div>
This is a simple example and may be obvious but using Angular I always have this problem, think about complex components: the #Input() would become many using this method, but creating a new component would increase files and complexity.
From this example you could say to create two components, one for title and another for button but that's only because this is a very simple case. Think about changing a component from "compact" mode to "expanded" and viceversa. On the one hand you may need to have the large component and on the other hand have it smaller in size and showing less information
Is there some guideline about this?
Thanks
I think it's important thinking about behavior within the context of your component. Is the button core to behavior of the title component? Does it make sense to not only display the button, but also handle its events within the context of the title component? If the answer is no, then at some granular level I'd split the components.
Here are some other things you can consider:
Anticipating that your title component may need some content wrapped with the title, you can use transclusion:
<div>
<h1>{{title}}</h1>
<ng-content></ng-content>
</div>
Then, in the parent, you'd do something like this:
<div>
<app-title-component title='title'>
<button>Some Button Text</button>
</app-title-component>
</div>
You could write a wrapper component that packages the title and the button together... ie:
<div>
<app-title-component></app-title-component>
<button>Some Button Text</button>
</div>
You could, as suggested, parameterize the configuration. I recommend thinking about if the behavior that you are parameterizing is part of the core behavior of the component. For example, if you want to paramaterize whether or not a legend shows on a chart, that makes sense because the legend is a core feature of the chart. But I probably wouldn't parameterize whether or not the chart should be followed by a raw data sheet. Instead I would create a new component for that and render them in sequence, because the data sheet is not part of the core behavior of the chart, even though sometimes I'd want to put them next to each other.
At the end of the day, you have to think about the decision in terms of your app, your app's future usability, and developer ease (e.g.- will it make sense to a future developer that this button is packaged with title).
I hope you find this helpful.

ag-Grid adding css classes

I have this simple requirement for my Angular application...namely adding a css class when expanding or collapsing a row for the purpose of highlighting said row. I have tried using gridOptions.getRowClass as per the docs https://www.ag-grid.com/javascript-grid-row-styles/#gsc.tab=0 but that appears to have no effect.
My html I have added an event handler that will fire a method once the row expands/collapses. My html looks as follows:
<ag-grid-angular #agGrid style="width: 100%; height:85vh" class="ag-material grid-Holdings"
[gridOptions]="gridOptions"
[rowData]="rowData"
...
(rowGroupOpened)="addRowClass($event)">
</ag-grid-angular>
...and in my grid.component.ts I have the method which should add/remove a css class:
addRowClass(params) {
this.gridOptions.getRowClass = (params) => {
if (params.node.expanded) {
return 'my-css-class';
}
}
}
One interesting thing is that when I add break points in Chrome dev console I see that it never executes return 'my-css-class' but I cannot find any information in the official documentation as to why. I am really struggling with ag-Grid in general, and Angular integration with ag-Grid in particular. If this does not work is it better to just do this in vanilla JS somehow... If so, how might I go about this without ag-Grid api?
ag-grid already toggles some css classes for you: ag-row-group-expanded (when expanded) and ag-row-group-contracted (when not expanded). You should be able to accomplish your goal by applying the necessary styles to these classes.

SVG flickering in Chrome in Angular app with data update

I'm working on an app in Angular 4. In one view I'm using some svgs placed between data that is retrieved from API request. It looks like this:
<div>
<div>
<svg><use xlink:href="./assets/svg/machine.svg#message-icon"></use></svg>
</div>
<div>{{machine.state}}</div>
<div>
<svg><use xlink:href="./assets/svg/machine.svg#settings-icon"></use></svg>
</div>
</div>
All svgs are put in one file (machine.svg), and are defined in < symbol > tags with ids.
Now my problem is when I set interval every 2 seconds to update data from API request, my svg icons appear to flicker with every update, but it only happens in Chrome.
I've checked the network logs and it seems that whole svg file is downloaded with every API request:
While in Mozilla everything works fine, svg is downloaded only once:
I've tried to put svg in < object > tag, but the requests are even more numerous. Putting every single svg in < img > tag seemed to resolve the problem with requests, but I would prefer to be in control of "fill" property. Putting the whole svg directly on the page solved problem too, but it doesn't seem to be a clean solution.
My question is if there is a way to retrieve svg from a file without Chrome downloading it constantly?
Also faced with this situation, the easiest way for me is to create a separate shared component that contains all the icons I need and which takes the parameters I need. For me, this is the fastest and most flexible solution.
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'ngx-svg-icon',
templateUrl: './svg-icon.component.html',
styleUrls: ['./svg-icon.component.scss']
})
export class SvgIconComponent implements OnInit {
#Input() icon: string;
#Input() fill: string;
constructor() {}
ngOnInit() {}
}
Usage:
<ngx-svg-icon [icon]="'settings'" [fill]="'#ededed'"></ngx-svg-icon>
Try to configure your server to add cache-control headers to those requests. I have the same issue on localhost, but on my production server icons come with cache-control: max-age=172800 header and are not re-requested again.

Angular 2 add custom component to Owl Carousel 2

I have created two components in Angular 2:
ReaderComponent: The one that initiates and controls all functionality to Owl Carousel (initiate, add slide, remove slide and so on)
PageComponent: Each slide is a PageComponent and has events to handle input from the user (click, pinch, doubletap)
The ReaderComponent is created at start of the application and initiates a request to a service to get all data for each of the PageComponents.
Everything works fine until we add a slide that is a PageComponent. I have tried to add the PageComponent selector to owl Carousel:
this.slider.trigger("add.owl.carousel", ["<my-page-component></my-page-component>"]);
This does add an element of <my-page-component> but does not render the template or handles any of the PageComponents events.
I have tried to add all the PageComponents to an array and render it in ReaderComponents template:
<div *ng-for="#page of pages">
<my-page></my-page>
</div>
This renders correct but by that time all pages is rendered Owl is already initiated and no pages is visible.
So to summarize all of this: I need to know how to add a custom component via javascript (in this case the add functionality of Owl)? Is this even possible? Or is there another way to handle this so that I can add PageComponent in any way?
The first method you mentioned would require you to force angular to re-check it's bindings. This is probably possible, but I don't know off the top of my head.
The second method is much easier. You can use the lifecycle events of the Page or Reader Components to trigger the adding. They are as follows:
export var LIFECYCLE_HOOKS_VALUES = [
LifecycleHooks.OnInit,
LifecycleHooks.OnDestroy,
LifecycleHooks.DoCheck,
LifecycleHooks.OnChanges,
LifecycleHooks.AfterContentInit,
LifecycleHooks.AfterContentChecked,
LifecycleHooks.AfterViewInit,
LifecycleHooks.AfterViewChecked
];
If you add a listener to your PageComponent class, you can probably use OnInit or AfterViewChecked and then get it to add it's own element reference to the carousel (basic example). From a quick look at their documentation, it doesn't look like owl supports adding a new element, so you could have the PageComponents all on your page somewhere hidden and then just add the raw html from the elementref, then remove it again in the OnDestroy function.
If you do it in ReaderComponent you should look at OnChanges or add some clever checks into the DoCheck function and then just get it to reload all items inside it (perhaps owl.reinit?). I've not used the owl carousel before so can't be more specific there I'm afraid.
These are exported from the angular class as interfaces, so you should be extending your classes from them. An example is available on the Angular 2 website here: https://angular.io/docs/ts/latest/api/lifecycle_hooks/AfterViewChecked-interface.html

Categories