= {start: minDate, end: maxDate};
+ console.log('getMaxRange dateRange', dateRange);
+ return dateRange;
+ }
+
+
public updateSeries(checked: boolean, index?: number) {
console.log('updateSeries', checked, index);
this.task.update(task => {
diff --git a/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.html b/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.html
index c18ec1bd2..fdd2c8c2e 100644
--- a/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.html
+++ b/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.html
@@ -1 +1,8 @@
-Timeseries Date Slider Goes Here
+
+
+ 2000
+ 2005
+ 2010
+ 2015
+ 2023
+
diff --git a/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.scss b/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.scss
index e69de29bb..d61d9383d 100644
--- a/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.scss
+++ b/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.scss
@@ -0,0 +1,42 @@
+/* Contenedor principal del slider */
+.slider {
+ margin: 0 auto; /* Centra el slider horizontalmente */
+ width: 100%; /* Ocupa todo el ancho disponible */
+ max-width: 95%; /* Opcional: Limita el ancho máximo para que no se extienda demasiado */
+ box-sizing: border-box;
+}
+
+.noUi-connect {
+ background-color: #007BFF; /* Color principal */
+ height: 6px; /* Ajusta el grosor de la barra conectada */
+}
+
+.noUi-handle {
+ background-color: #FFFFFF;
+ border: 2px solid #007BFF; /* Borde que combine con el color principal */
+ border-radius: 50%; /* Hace que el control sea redondo */
+ height: 20px;
+ width: 20px;
+}
+
+.noUi-tooltip {
+ background-color: #333;
+ color: #fff;
+ border-radius: 4px;
+ font-size: 12px;
+}
+
+/* Estilos de las etiquetas debajo del slider */
+.slider-labels {
+ display: flex;
+ justify-content: space-between;
+ width: 100%; /* Ocupa todo el ancho del slider */
+ max-width: 95%; /* Limita el ancho para alinearse con el slider */
+ margin: 10px auto 0; /* Centra y da espacio superior al slider */
+ box-sizing: border-box;
+}
+
+.slider-labels span {
+ font-size: 12px;
+ color: #666; /* Color de las etiquetas */
+}
diff --git a/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.ts b/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.ts
index 7eaebdbc3..8d082ec81 100644
--- a/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.ts
+++ b/src/app/components/timeseries-chart/timeseries-chart-temporal-slider/timeseries-chart-temporal-slider.component.ts
@@ -1,12 +1,131 @@
-import { Component } from '@angular/core';
+import {Component, ElementRef, ViewChild, OnInit, Input, OnChanges, SimpleChanges} from '@angular/core';
+import * as noUiSlider from 'nouislider';
+import { Store } from "@ngrx/store";
+import { AppState } from "@store";
+import * as models from "@models";
+import {Observable, Subject} from 'rxjs';
+import {UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators} from "@angular/forms";
+import * as filtersStore from "@store/filters";
+import {SubSink} from "subsink";
+import {debounceTime, distinctUntilChanged} from "rxjs/operators";
@Component({
selector: 'app-timeseries-chart-temporal-slider',
standalone: true,
- imports: [],
templateUrl: './timeseries-chart-temporal-slider.component.html',
- styleUrl: './timeseries-chart-temporal-slider.component.scss'
+ styleUrls: ['./timeseries-chart-temporal-slider.component.scss']
})
-export class TimeseriesChartTemporalSliderComponent {
+export class TimeseriesChartTemporalSliderComponent implements OnInit, OnChanges {
+ @Input() maxRange: models.Range = {start: 0, end: 0};
+ @ViewChild('slider', { static: true }) sliderRef: ElementRef;
+ public daysRange: models.Range = {start: 0, end: 0};
+ public lastRange: models.Range = {start: 0, end: 0};
+ public daysValues$ = new Subject();
+ public slider;
+
+ options: UntypedFormGroup;
+
+ daysControl: UntypedFormControl;
+
+ private firstLoad = true;
+ private subs = new SubSink();
+
+ constructor(
+ private store$: Store,
+ fb: UntypedFormBuilder
+ ) {
+ this.daysControl = new UntypedFormControl(this.daysRange, Validators.min(0));
+
+ this.options = fb.group({
+ days: this.daysControl,
+ });
+ }
+
+ ngOnInit() {
+ this.daysControl = new UntypedFormControl(this.daysRange, Validators.min(0));
+ const daysSliderRef = this.makeDaysSlider(this.sliderRef);
+ const daysValues$ = daysSliderRef.daysValues;
+
+ // this.tempSlider = tempSlider;
+
+ this.subs.add(
+ daysValues$.subscribe(
+ range => {
+ console.log(range);
+ const action = new filtersStore.SetTemporalRange({ start: range[0], end: range[1] });
+ this.store$.dispatch(action);
+ }
+ )
+ );
+
+ this.subs.add(
+ this.store$.select(filtersStore.getTemporalRange).subscribe(
+ temp => {
+ this.daysRange = {start: temp.start, end: temp.end};
+ if (this.firstLoad) {
+ this.firstLoad = false;
+ this.slider.set([temp.start, temp.end]);
+ }
+ }
+ )
+ );
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes.maxRange && changes.maxRange.currentValue) {
+ this.maxRange = changes.maxRange.currentValue;
+ console.log('changes start time:', this.maxRange.start.valueOf());
+ console.log('changes end time:', this.maxRange.end.valueOf());
+ this.daysRange = {start: this.maxRange.start.valueOf(),
+ end: this.maxRange.end.valueOf()};
+ if (this.firstLoad) {
+ this.firstLoad = false;
+ this.slider.set([this.daysRange.start, this.daysRange.end]);
+ }
+ // this.slider.noUiSlider.updateOptions({
+ // start: [this.maxRange.start.valueOf(), this.maxRange.end.valueOf()],
+ // range: {
+ // 'min': this.maxRange.start.valueOf(),
+ // 'max': this.maxRange.end.valueOf()
+ // }
+ }
+ }
+
+ public updateDaysOffset() {
+ this.options.controls.days.setValue(this.daysRange);
+ this.daysValues$.next([this.daysRange.start, this.daysRange.end] );
+ }
+
+ public makeDaysSlider(filterRef: ElementRef): {slider: any, daysValues: Observable} {
+ console.log('makeDaysSlider maxRange', this.maxRange);
+ this.slider = noUiSlider.create(filterRef.nativeElement, {
+ start: [2000, 2023],
+ behaviour: 'tap-drag',
+ tooltips: false,
+ connect: true,
+ step: 1,
+ range: {
+ 'min': 2000,
+ 'max': 2023
+ }
+ });
+
+ this.slider.on('update', (values, _) => {
+ this.daysValues$.next(values.map(v => +v));
+ });
+
+ return {
+ slider: this.slider,
+ daysValues: this.daysValues$.asObservable().pipe(
+ debounceTime(500),
+ distinctUntilChanged()
+ )
+ };
+
+ }
+
+ ngOnDestroy() {
+ this.subs.unsubscribe();
+ }
}
diff --git a/src/app/components/timeseries-chart/timeseries-chart.component.ts b/src/app/components/timeseries-chart/timeseries-chart.component.ts
index a185c9c91..865dd6e06 100644
--- a/src/app/components/timeseries-chart/timeseries-chart.component.ts
+++ b/src/app/components/timeseries-chart/timeseries-chart.component.ts
@@ -32,6 +32,8 @@ interface DataReady {
values: TimeSeriesData[]
}
+const unSelectedColor = '#9F9F9F9F';
+
@Component({
selector: 'app-timeseries-chart',
templateUrl: './timeseries-chart.component.html',
@@ -350,6 +352,7 @@ export class TimeseriesChartComponent implements OnInit, OnDestroy {
// add the dots
this.dots = this.svg.append('g')
+ .attr("id", "dotsParent")
.selectAll("myDots")
.data(this.dataReadyForChart)
.enter()
@@ -383,8 +386,7 @@ export class TimeseriesChartComponent implements OnInit, OnDestroy {
}
// When the pointer moves, find the closest point, update the interactive tip, and highlight
- // the corresponding line. Note: we don't actually use Voronoi here, since an exhaustive search
- // is fast enough.
+ // the corresponding line.
private pointerMoved(event, lines, dots, points) {
console.log('pointerMoved');
if (typeof points === 'undefined') { return; }
@@ -392,57 +394,49 @@ export class TimeseriesChartComponent implements OnInit, OnDestroy {
const [xm, ym] = d3.pointer(event);
const i = d3.leastIndex(points, ([x, y]) => Math.hypot(Number(x) - xm, Number(y) - ym));
if (typeof points[i] === 'undefined') { return; }
- const [x, y, k] = points[i];
+ const [_x, _y, k] = points[i];
let colorName: string;
let dClassName: string;
- console.log('points', points);
- console.log('points[i]', points[i]);
- console.log('dots', dots);
- console.log('xm', xm, 'ym', ym);
- console.log('i', i);
- console.log('x', x, 'y', y, 'k', k);
+ // set the color of the selected line to the color of the series; make all other lines grey
lines.style("stroke", (d: DataReady)=> {
if (d.name === k) {
- dClassName = 'g.' + d.name.replace(/\W/g, '');
+ dClassName = '.' + d.name.replace(/\W/g, '');
colorName = this.gColorPalette(d.name);
return colorName;
}
- return '#ddd';
+ return unSelectedColor;
+ });
+ // sort the lines so that the selected line is on top
+ lines.selectAll("path").sort(function (a, _b) {
+ if (a.attr('stroke') != colorName) return -1;
+ else return 1;
+ });
+ this.svg.selectAll('circle').style("fill", unSelectedColor);
+ this.svg.selectAll(dClassName + ' ' + 'circle').style("fill", colorName);
+ this.svg.selectAll("dotsParent").sort(function (a, _b) {
+ // @ts-ignore
+ if (a.attr('class') != dClassName) return -1;
+ else return 1;
});
- dots.selectAll('circle').style("fill", '#ddd');
- dots.selectAll(dClassName).style("fill", 'red');
-
- // dots.selectAll('myDots').data(this.dataReadyForChart).style("fill", (d: DataReady) => {
- // dots.selectAll('circle').style("fill", (d: DataReady) => {
- // console.log('dots d:', d);
- // if (d.name === k) {
- // return this.gColorPalette(d.name);
- // }
- // return '#ddd';
- // });
- // this.lines.style("stroke", "red").filter(({ z }) => z === k).raise();
- // .attr("stroke", function (d: DataReady) { return colorPalette(d.name) })
- // lines.style("stroke", ({z}) => z === k ? null : "#ddd").filter(({z}) => z === k).raise();
- // dots.attr("transform", `translate(${x},${y})`);
dots.select("text").text(k);
- // this.svg.property("value", this.dataReadyForChart[i]).dispatch("input", {bubbles: true});
}
private pointerEntered(lines, dots) {
console.log('pointerEntered lines, dots', lines, dots);
- lines.style("mix-blend-mode", null).style("stroke", "#ddd");
+ lines.style("mix-blend-mode", null).style("stroke", unSelectedColor);
dots.attr("display", null);
}
private pointerLeft(lines, dots) {
console.log('pointerLeft', lines, dots);
+ let colorName: string;
+ let dClassName: string;
lines.style("stroke", (d: DataReady)=> {
- return this.gColorPalette(d.name);
- })
- // lines.style("mix-blend-mode", "multiply").style("stroke", null);
- // dots.attr("display", "none");
- // this.svg.node().value = null;
- // self.svg.dispatch("input", {bubbles: true});
+ dClassName = '.' + d.name.replace(/\W/g, '');
+ colorName = this.gColorPalette(d.name);
+ this.svg.selectAll(dClassName + ' ' + 'circle').style("fill", colorName);
+ return colorName;
+ });
}
private updateChart() {