Implement Angular range slider using months - html

I'm implementing a range slider by months using ngx-slider.
I want the value to be 1-12 and the display legend to be January-December.
I achieve it using the code below.
export class AppComponent {
minValue: number = 3;
maxValue: number = 9;
options: Options = {
floor: 1,
ceil: 12,
translate: (value: number, label: LabelType): string => {
switch (label) {
case LabelType.Low:
return "<b>March</b>";
case LabelType.High:
return "<b>October</b>";
default:
return "$" + value;
}
}
};
}
And the result is this one:
Link to the image result
How can I remove the dollar sign of the start and end value?
Or do you have better recommendation npm library for range slider for months?

Hello this time slider I used in the project
https://refreshless.com/nouislider/examples/
You can change the style using CSS,
Hope it can be adopted.

Related

Pagination on Angular Material Design - Show page numbers or remove the row count

Angular 6/7, Material Design.
Since I don't have access to the total number of items the item count is irrelevant (the box in the screen shot).
How do I remove the item count completely? Or alternatively show the page I'm currently on instead of the item count?
<mat-paginator
itemsPerPageLabel="Items per page"
(page)="changePage()"
[length]="resultsLength"
[pageSizeOptions]="[10, 100]">
</mat-paginator>
Remove the range label by inserting in global CSS
.mat-paginator-range-label {
display: none;
}
Insert page number instead (of course based on your API - you might not have the page info!) by inserting in your component
ngAfterViewChecked() {
const list = document.getElementsByClassName('mat-paginator-range-label');
list[0].innerHTML = 'Page: ' + this.page.toString();
}
and of course delete the CSS rule above!
Paginator now looks like this
I just modified Johan Faerch's solution to fit more to your question.
Create method which has two parameters, one for matpaginator and another for list of HTMLCollectionOf
paginatorList: HTMLCollectionOf<Element>;
onPaginateChange(paginator: MatPaginator, list: HTMLCollectionOf<Element>) {
setTimeout((idx) => {
let from = (paginator.pageSize * paginator.pageIndex) + 1;
let to = (paginator.length < paginator.pageSize * (paginator.pageIndex + 1))
? paginator.length
: paginator.pageSize * (paginator.pageIndex + 1);
let toFrom = (paginator.length == 0) ? 0 : `${from} - ${to}`;
let pageNumber = (paginator.length == 0) ? `0 of 0` : `${paginator.pageIndex + 1} of ${paginator.getNumberOfPages()}`;
let rows = `Page ${pageNumber} (${toFrom} of ${paginator.length})`;
if (list.length >= 1)
list[0].innerHTML = rows;
}, 0, paginator.pageIndex);
}
How to call this method? you can initialize this on ngAfterViewInit()
ngAfterViewInit(): void {
this.paginatorList = document.getElementsByClassName('mat-paginator-range-label');
this.onPaginateChange(this.paginator, this.paginatorList);
this.paginator.page.subscribe(() => { // this is page change event
onPaginateChange(this.paginator, this.paginatorList);
});
}
Include this method in your css file(note: do not include in the main styles.css file)
.mat-paginator-range-label {
display: none;
}
You can call onPaginateChange(this.paginator, this.paginatorList) functions wherever you need to change the page number details other than clicking on the navigation buttons on the mat paginator.
Result looks like this

Absolute positioning divs in Angular

I'm developing a calendar component for Angular and I need to position some div element (representing events) in a grid.
this.days.forEach((day: SchedulerViewDay, dayIndex: number) => {
day.events = this.getEventsInPeriod({...}).map((event: CalendarSchedulerEvent) => {
const segmentDuration: number = 60.0 / this.hourSegments;
const dayStartDate: Date =
setSeconds(setMinutes(setHours(
setDate(setMonth(setYear(new Date(), day.date.getFullYear()), day.date.getMonth()), day.date.getDate()), this.dayStartHour), this.dayStartMinute), 0);
const segmentsNumber: number = (differenceInMinutes(event.start, dayStartDate) / segmentDuration);
return <CalendarSchedulerEvent>{
...
height: this.hourSegmentHeight * (differenceInMinutes(event.end, event.start) / segmentDuration),
top: (this.hourSegmentHeight * segmentsNumber)
};
});
With that code, this is the result:
As you can see the event div is not correctly positioned. I think that this is due to the grid cell borders so i've applied that fix:
top: (this.hourSegmentHeight * segmentsNumber) + (segmentsNumber / 2) // 1px for each separator line
Each border is 1px width and I consider only solid borders and not dashed ones (this is the motivation of / 2.
This almost resolve the problem. There's still a misalignment.
Moreover this is not an elegant solution but I can't figure out to make it more clean.
How can I correctly position these divs?
Thank you very much!
EDIT: implementation of inorganik's solution (first answer)
I've created the following mixins in my scss file:
#mixin day-x($attr, $attr-count: 7, $attr-steps: 1, $unit: '%') {
$attr-list: null;
#for $i from 1 through $attr-count {
$attr-value: $attr-steps * $i;
&.day#{$i} {
#{$attr}: #{$attr-value}#{$unit};
}
$attr-list: append($attr-list, unquote(".#{$attr}-#{$attr-value}"), comma);
}
#{$attr-list} {
//append style to all classes
}
}
#mixin time-x($attr, $start-hour: 0, $end-hour: 23, $attr-steps: 2, $minutes-steps: 15, $unit: '%') {
$attr-list: null;
$hours: $start-hour;
$minutes: 0;
$attr-count: ((($end-hour + 1) - $start-hour) * 60) / $minutes-steps;
#for $i from 0 through $attr-count {
$attr-value: $attr-steps * $i;
#if($i > 0) {
$minutes: $minutes + $minutes-steps;
#if($minutes == 60) {
$minutes: 0;
$hours: $hours + 1;
}
}
$hoursString: '#{$hours}';
#if($hours < 10) {
$hoursString: '0#{$hours}';
}
$minutesString: '#{$minutes}';
#if($minutes < 10) {
$minutesString: '0#{$minutes}';
}
&.time#{$hoursString}#{$minutesString} {
#{$attr}: #{$attr-value}#{$unit};
}
$attr-list: append($attr-list, unquote(".#{$attr}-#{$attr-value}"), comma);
}
#{$attr-list} {
//append style to all classes
}
}
#mixin length-x($attr, $attr-steps: 2, $minutes-steps: 15, $unit: '%') {
$attr-list: null;
$attr-count: 24 * 60 / $minutes-steps;
#for $i from 0 through $attr-count {
$attr-name: $minutes-steps * $i;
$attr-value: $attr-steps * $i;
&.length#{$attr-name} {
#{$attr}: #{$attr-value}#{$unit};
}
$attr-list: append($attr-list, unquote(".#{$attr}-#{$attr-value}"), comma);
}
#{$attr-list} {
//append style to all classes
}
}
#include day-x('left', 7, 5);
#include time-x('top', 6, 22, 1.47);
#include length-x('height', 1.47);
Then I apply these styles with [ngClass] attribute, calling the following method (I don't need getDay, for now):
getPositioningClasses(event: CalendarSchedulerEvent): string {
const classes: string[] = [
this.getDayClass(event.start),
this.getTimeClass(event.start),
this.getLengthClass(differenceInMinutes(event.end, event.start))
];
return classes.join(' ');
}
private getDayClass(date: Date): string {
return '';
}
private getTimeClass(date: Date): string {
let hours: string = date.getHours() < 10 ? `0${date.getHours()}` : `${date.getHours()}`;
let minutes: string = date.getMinutes() < 10 ? `0${date.getMinutes()}` : `${date.getMinutes()}`;
return `time${hours}${minutes}`;
}
private getLengthClass(durationInMinutes: number): string {
return `length${durationInMinutes}`;
}
This is the result:
Graphically it works like a charm but I need to keep segments height (58px) aligned to the percentage increment used to generate positioning classes (now it's 1.47%).
Is there a way to make those variables (segments height and percentage height and top position increments) from Angular code, making the segment height configurable by the user and let these increments to adapt itself?
Thank you very much!
EDIT 2: another problem
Hi again! I've encountered another problem.
The component hour range it is not fixed but it is configurable from outside.
In the example in the question is 6AM - 22PM. For that range the percentage of 1.47 is fine but if I change the range (eg. 0AM - 22AM) the calendar height is higher and the percentage is not ok anymore.
I think I need to calculate those positioning values from Typescript. But I can't figure out how to do that.
To try something I'm trying this:
ngAfterViewInit(): void {
this.calendarContainerHeight = this.calendarContainer.nativeElement.clientHeight;
const segmentPercentage: number = 100.0 * this.hourSegmentHeight / this.calendarContainerHeight;
console.log("calendarContainerHeight: ", this.calendarContainerHeight);
console.log("segmentPercentage: ", segmentPercentage);
setTimeout(() => {
this.view.days.forEach(d => {
d.events.forEach((event: CalendarSchedulerEvent) => {
let hours: number = event.start.getHours();
if (this.dayStartHour > 0) { hours = hours - this.dayStartHour; }
const numberOfSegments: number = hours * this.hourSegments;
event.top = numberOfSegments * segmentPercentage;
});
});
});
}
Then I've added [style.top.%]="event.top" to the event div. This is the result (ignore height for now, I've not managed them yet):
As you can see the percentage is not accurate and those events which are in the middle of the day (or towards the end of the day) are not positioned correctly.
How could I solve this problem?
Thank you very much, again!
It seems like a brittle solution to calculate pixel distances in code. I would use CSS classes. You can have an SCSS mixin generate all the possible classes for you for days, times and segment lengths. Make 3 methods that return css class strings, one for day, one for time, and one for segment length which you can apply with [ngClass]. Then a method which calls each one, something like below. This assumes you have an array of segments which store date/time/length info on them:
getPositioningClasses(segment: any): string {
const classes = [this.getDayClass(segment.dayStartDate), this.getTimeClass(segment.dayStartDate), this.getLengthClass(segment.timeLength)];
return classes.join(' ');
}
So your markup might end up looking like this:
<segment [ngClass]="getPositioningClasses(segment)"></segment>
Compiled:
<segment class="day0 time1430 length130"></segment>
day0 - you could have 0-X for however many days you display. This class is just responsible for distance from the left. Use percentages if you can so that the layout can be flexible.
time1430 in this case 2:30pm - This class is just responsible for distance from the top
length130 in this case an hour and a half. Ideally set the smallest increment of time (15m?) as a percentage height, then let the mixin multiply the increment for each possible length.
Also to make sure you don't have to factor in borders, put the box-sizing: border-box css rule on pretty much everything.

Typescript - Conditionally create duplicate elements

I have a variable which users of my application can modify, say:
let myValue = 3;
In my html I wish to create as many duplicates of an element as the value of the variable is.
In my case, myValue is 3, then we create the div element 3 times.
<div>I am a duplicate.</div>
<div>I am a duplicate.</div>
<div>I am a duplicate.</div>
myValue can be a higher or lower number as well. Whatever it is, I would like to have that many duplicates of my element. How can I achieve this?
P.S. I am still new to Angular and Typescript so please don't go hard on me if this is a rather simple question.
// creates an array in TS file based on myValue
duplicates = Array(myValue).fill(null).map( (x,index) => index );
// use ngFor in HTML
<div ngFor="let duplicate of duplicates">
<div>I am a duplicate. {{ duplicate }}</div>
</div>
This can be done using the Iterator Protocol.
HTML
<ol>
<li *ngFor="let i of value">{{ i }}</li>
</ol>
TypeScript
export class AppComponent {
title = 'app';
_value = 3;
get value(){
let iterable = {
length: this._value,
index: 0,
next: () => {
if (iterable.index < iterable.length) {
return {value: iterable.index++, done: false};
} else {
iterable.index = 0;
return {done: true};
}
},
[Symbol.iterator]: function() { return this }
};
return iterable;
}
}

How to add legend for a bar chart with different colors in dc.js?

Below is the code snippet for a barchart with colored bars:
var Dim2 = ndx.dimension(function(d){return [d.SNo, d.something ]});
var Group2 = Dim2.group().reduceSum(function(d){ return d.someId; });
var someColors = d3.scale.ordinal().domain(["a1","a2","a3","a4","a5","a6","a7","a8"])
.range(["#2980B9","#00FFFF","#008000","#FFC300","#FF5733","#D1AEF1","#C0C0C0","#000000"]);
barChart2
.height(250)
.width(1000)
.brushOn(false)
.mouseZoomable(true)
.x(d3.scale.linear().domain([600,800]))
.elasticY(false)
.dimension(Dim2)
.group(Group2)
.keyAccessor(function(d){ return d.key[0]; })
.valueAccessor(function(d){return d.value; })
.colors(someColors)
.colorAccessor(function(d){return d.key[1]; });
How do I add a legend to this chart?
Using composite keys in crossfilter is really tricky, and I don't recommend it unless you really need it.
Crossfilter only understands scalars, so even though you can produce dimension and group keys which are arrays, and retrieve them correctly, crossfilter is going to coerce those arrays to strings, and that can cause trouble.
Here, what is happening is that Group2.all() iterates over your data in string order, so you get keys in the order
[1, "a1"], [10, "a3"], [11, "a4"], [12, "a5"], [2, "a3"], ...
Without changing the shape of your data, one way around this is to sort the data in your legendables function:
barChart2.legendables = function() {
return Group2.all().sort((a,b) => a.key[0] - b.key[0])
.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
An unrelated problem is that dc.js takes the X domain very literally, so even though [1,12] contains all the values, the last bar was not shown because the right side ends right at 12 and the bar is drawn between 12 and 13.
So:
.x(d3.scale.linear().domain([1,13]))
Now the legend matches the data!
Fork of your fiddle (also with dc.css).
EDIT: Of course, you want the legend items unique, too. You can define uniq like this:
function uniq(a, kf) {
var seen = [];
return a.filter(x => seen[kf(x)] ? false : (seen[kf(x)] = true));
}
Adding a step to legendables:
barChart2.legendables = function() {
var vals = uniq(Group2.all(), kv => kv.key[1]),
sorted = vals.sort((a,b) => a.key[1] > b.key[1] ? 1 : -1);
// or in X order: sorted = vals.sort((a,b) => a.key[0] - b.key[0]);
return sorted.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
Note that we're sorting by the string value of d.something which lands in key[1]. As shown in the comment, sorting by x order (d.SNo, key[0]) is possible too. I wouldn't recommend sorting by y since that's a reduceSum.
Result, sorted and uniq'd:
New fiddle.

kendo grid sorting not working in Chrome

I have problem with sorting in Kendo grid. Here is my example: http://dojo.telerik.com/iVATi
In IE sorting works fine: default view and asc sorting view are the same: first going elements starting with symbols, second elements with a-z letters, third elements with а-я letters. But in Chrome and Firefox I see three other results:
1). default view: first going element starting with symbols, second elements with a-z letters, third elements with а-я letters. (correct!)
2). asc sorting: first going elements starting with symbols, second elements with а-я letters, third elements with a-z letters. (bad!)
3). desc sorting: first going elements with z-a letters, second elements with я-а letters, third sorted elements with symbols. (correct!)
The problem is caused by Chrome's unstable sorting and adding an auxiliary data field is the standard way to resolve this limitation.
In case you don't want to add indexes in the data items array, it is also possible to add them on the fly with schema.parse:
var dataSource = new kendo.data.DataSource({
data: [
{ Name: "!asdgad" },
{ Name: "#sgjkhsh" },
{ Name: "adfadfka" },
{ Name: "tgjbndgnb" },
{ Name: "xsdfvks" },
{ Name: "абдваолптрв" },
{ Name: "пролрлитс" },
{ Name: "юатроваро" },
{ Name: "юдвлоитвт" }
],
schema: {
parse: function(data) {
for (var i = 0; i < data.length; i++) {
data[i].index = i;
}
return data;
}
}
});
You will still need to use a custom comparer function though.
I solved this problem. I extended sorted datasource with index field and added comparer-function by index for field name:
http://dojo.telerik.com/UKimo
May be exists other solutions?