Compare commits

...

2 Commits

Author SHA1 Message Date
452030de5e use more efficient way to determine needed bytes for variable byte
encoding

I experimented with a few branch-free variants, but they were slower
than just using ternary operators and readable code.
2024-04-01 12:44:30 +02:00
6b8e3d2089 add component for a custom date picker that also knows relative date ranges like 'last month' 2024-03-31 14:48:33 +02:00
10 changed files with 459 additions and 86 deletions

View File

@@ -1,30 +1,31 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { VisualizationPageComponent } from './visualization-page/visualization-page.component';
import { MainPageComponent } from './main-page/main-page.component';
import { UploadPageComponent } from './upload-page/upload-page.component';
import { HelpPageComponent } from './help-page/help-page.component';
import { DashboardPageComponent } from './dashboard-page/dashboard-page.component';
import { DashboardComponent } from './dashboard-page/dashboard/dashboard.component';
import { CustomizableGridComponent } from './customizable-grid/customizable-grid.component';
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { VisualizationPageComponent } from "./visualization-page/visualization-page.component";
import { MainPageComponent } from "./main-page/main-page.component";
import { UploadPageComponent } from "./upload-page/upload-page.component";
import { HelpPageComponent } from "./help-page/help-page.component";
import { DashboardPageComponent } from "./dashboard-page/dashboard-page.component";
import { DashboardComponent } from "./dashboard-page/dashboard/dashboard.component";
import { CustomizableGridComponent } from "./customizable-grid/customizable-grid.component";
import { DatePickerTestComponent } from "./components/datepicker/date-picker-test.component";
const routes: Routes = [
{ path: '', component: MainPageComponent},
{ path: 'vis', component: VisualizationPageComponent },
{ path: 'dashboard', component: DashboardPageComponent},
{ path: 'dashboard/:id', component: DashboardComponent},
{ path: 'upload', component: UploadPageComponent },
{ path: 'grid', component: CustomizableGridComponent },
{ path: 'help', component: HelpPageComponent },
{ path: "", component: MainPageComponent },
{ path: "vis", component: VisualizationPageComponent },
{ path: "dashboard", component: DashboardPageComponent },
{ path: "dashboard/:id", component: DashboardComponent },
{ path: "testing/datepicker", component: DatePickerTestComponent },
{ path: "upload", component: UploadPageComponent },
{ path: "grid", component: CustomizableGridComponent },
{ path: "help", component: HelpPageComponent },
// { path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {})
RouterModule.forRoot(routes, {}),
],
declarations: [],
exports: [RouterModule]
exports: [RouterModule],
})
export class AppRoutingModule { }
export class AppRoutingModule {}

View File

@@ -1,54 +1,65 @@
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule, enableProdMode } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { enableProdMode, NgModule } from "@angular/core";
import { HttpClientModule } from "@angular/common/http";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MainPageComponent } from './main-page/main-page.component';
import { HelpPageComponent } from './help-page/help-page.component';
import { UploadPageComponent } from './upload-page/upload-page.component';
import { VisualizationPageComponent } from './visualization-page/visualization-page.component';
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { MainPageComponent } from "./main-page/main-page.component";
import { HelpPageComponent } from "./help-page/help-page.component";
import { UploadPageComponent } from "./upload-page/upload-page.component";
import { VisualizationPageComponent } from "./visualization-page/visualization-page.component";
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatButtonModule} from '@angular/material/button';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatSelectModule} from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatRadioModule} from '@angular/material/radio';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {OverlayModule} from '@angular/cdk/overlay';
import { YAxisDefinitionComponent } from './y-axis-definition/y-axis-definition.component';
import { QueryAutocompleteComponent } from './query-autocomplete/query-autocomplete.component';
import { LimitByComponent } from './limit-by/limit-by.component';
import { PlotDetailsComponent } from './plot-details/plot-details.component';
import { PlotViewComponent } from './plot-view/plot-view.component';
import { GalleryViewComponent, GalleryItemView, GalleryFilterView } from './gallery-view/gallery-view.component';
import { ImageToggleComponent } from './image-toggle/image-toggle.component';
import { DashboardPageComponent } from './dashboard-page/dashboard-page.component';
import { NewDashboardComponent } from './dashboard-page/new-dashboard/new-dashboard.component';
import { MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog';
import {MatTableModule} from '@angular/material/table';
import {MatGridListModule} from '@angular/material/grid-list';
import {MatCardModule} from '@angular/material/card';
import {MatBadgeModule} from '@angular/material/badge';
import { DashboardComponent } from './dashboard-page/dashboard/dashboard.component';
import { AddTextDialogComponent } from './dashboard-page/dashboard/add-text-dialog/add-text-dialog.component';
import { TextWidgetComponent } from './dashboard-page/dashboard/text-widget/text-widget.component';
import { AddPlotDialogComponent } from './dashboard-page/dashboard/add-plot-dialog/add-plot-dialog.component';
import { PlotWidgetComponent } from './dashboard-page/dashboard/plot-widget/plot-widget.component';
import { FullScreenPlotDialogComponent } from './dashboard-page/dashboard/full-screen-plot-dialog/full-screen-plot-dialog.component';
import { CustomizableGridComponent } from './customizable-grid/customizable-grid.component';
import { DatePickerComponent } from "./components/datepicker/date-picker.component";
import { DatePickerTestComponent } from "./components/datepicker/date-picker-test.component";
import {DragDropModule} from '@angular/cdk/drag-drop';
import { ConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
import { FocusDirective } from './focus.directive';
import { MarkdownModule } from 'ngx-markdown';
import { MatAutocompleteModule } from "@angular/material/autocomplete";
import { MatButtonModule } from "@angular/material/button";
import { MatCheckboxModule } from "@angular/material/checkbox";
import { MatSelectModule } from "@angular/material/select";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatRadioModule } from "@angular/material/radio";
import { MatSnackBarModule } from "@angular/material/snack-bar";
import { MatTooltipModule } from "@angular/material/tooltip";
import { OverlayModule } from "@angular/cdk/overlay";
import { YAxisDefinitionComponent } from "./y-axis-definition/y-axis-definition.component";
import { QueryAutocompleteComponent } from "./query-autocomplete/query-autocomplete.component";
import { LimitByComponent } from "./limit-by/limit-by.component";
import { PlotDetailsComponent } from "./plot-details/plot-details.component";
import { PlotViewComponent } from "./plot-view/plot-view.component";
import {
GalleryFilterView,
GalleryItemView,
GalleryViewComponent,
} from "./gallery-view/gallery-view.component";
import { ImageToggleComponent } from "./image-toggle/image-toggle.component";
import { DashboardPageComponent } from "./dashboard-page/dashboard-page.component";
import { NewDashboardComponent } from "./dashboard-page/new-dashboard/new-dashboard.component";
import {
MAT_DIALOG_DEFAULT_OPTIONS,
MatDialogModule,
} from "@angular/material/dialog";
import { MatTabsModule } from "@angular/material/tabs";
import { MatTableModule } from "@angular/material/table";
import { MatGridListModule } from "@angular/material/grid-list";
import { MatCardModule } from "@angular/material/card";
import { MatBadgeModule } from "@angular/material/badge";
import { DashboardComponent } from "./dashboard-page/dashboard/dashboard.component";
import { AddTextDialogComponent } from "./dashboard-page/dashboard/add-text-dialog/add-text-dialog.component";
import { TextWidgetComponent } from "./dashboard-page/dashboard/text-widget/text-widget.component";
import { AddPlotDialogComponent } from "./dashboard-page/dashboard/add-plot-dialog/add-plot-dialog.component";
import { PlotWidgetComponent } from "./dashboard-page/dashboard/plot-widget/plot-widget.component";
import { FullScreenPlotDialogComponent } from "./dashboard-page/dashboard/full-screen-plot-dialog/full-screen-plot-dialog.component";
import { CustomizableGridComponent } from "./customizable-grid/customizable-grid.component";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { ConfirmationDialogComponent } from "./confirmation-dialog/confirmation-dialog.component";
import { FocusDirective } from "./focus.directive";
import { MarkdownModule } from "ngx-markdown";
@NgModule({
declarations: [
@@ -56,6 +67,8 @@ import { MarkdownModule } from 'ngx-markdown';
MainPageComponent,
HelpPageComponent,
UploadPageComponent,
DatePickerComponent,
DatePickerTestComponent,
VisualizationPageComponent,
YAxisDefinitionComponent,
QueryAutocompleteComponent,
@@ -76,7 +89,7 @@ import { MarkdownModule } from 'ngx-markdown';
FullScreenPlotDialogComponent,
CustomizableGridComponent,
ConfirmationDialogComponent,
FocusDirective
FocusDirective,
],
imports: [
MarkdownModule.forRoot(),
@@ -99,15 +112,19 @@ import { MarkdownModule } from 'ngx-markdown';
MatProgressSpinnerModule,
MatSelectModule,
MatSnackBarModule,
MatTabsModule,
MatTableModule,
MatTooltipModule,
BrowserAnimationsModule,
HttpClientModule,
OverlayModule
OverlayModule,
],
providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}],
bootstrap: [AppComponent]
providers: [{
provide: MAT_DIALOG_DEFAULT_OPTIONS,
useValue: { hasBackdrop: true },
}],
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}
enableProdMode()
enableProdMode();

View File

@@ -0,0 +1,5 @@
<style>
</style>
<app-date-picker></app-date-picker>

View File

@@ -0,0 +1,12 @@
import { Component} from '@angular/core';
@Component({
selector: 'app-date-picker-test',
templateUrl: './date-picker-test.component.html'
})
export class DatePickerTestComponent {
constructor(){
}
}

View File

@@ -0,0 +1,207 @@
<style>
#date-box {
width: 23.5em;
}
.header-box {
display: flex;
justify-content: end;
}
.date-picker-overlay {
width: 500px;
transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
background-color: white;
}
.tab-quick {
display: flex;
flex-direction: row;
gap: 2em;
}
.tab-quick-column {
display: flex;
flex-direction: column;
}
.date-picker-form-field {
width: 23.5em;
}
</style>
<div class="header-box">
<button
mat-button
matTooltip="Date Picker"
(click)="isOpen = !isOpen"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
>
{{ dateValue.display }}
</button>
</div>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
>
<div class="date-picker-overlay">
<mat-tab-group
animationDuration="0ms"
(selectedTabChange)="tabChange()"
[(selectedIndex)]="selectedTabIndex"
>
<mat-tab label="Quick">
<div class="tab-quick">
<div class="tab-quick-column">
<button mat-button (click)="applyQuick('BD/P1D', 'today')">
Today
</button>
<button mat-button (click)="applyQuick('B-1D/P1D', 'yesterday')">
Yesterday
</button>
<button mat-button (click)="applyQuick('BW/EW', 'this week')">
This Week
</button>
<button mat-button (click)="applyQuick('BM/EM', 'this month')">
This Month
</button>
<button mat-button (click)="applyQuick('BQ/EQ', 'this quarter')">
This Quarter
</button>
<button mat-button (click)="applyQuick('BY/EY', 'this year')">
This Year
</button>
</div>
<div class="tab-quick-column">
<button mat-button (click)="applyQuick('B-7D/ED', 'last 7 days')">
Last 7 Days
</button>
<button mat-button (click)="applyQuick('B-1W/ED', 'last week')">
Last Week
</button>
<button mat-button (click)="applyQuick('B-30D/ED', 'last 30 days')">
Last 30 Days
</button>
<button mat-button (click)="applyQuick('B-1M/E-1M', 'last month')">
Last Month
</button>
<button
mat-button
(click)="applyQuick('B-3M/E-1M', 'last 3 months')"
>
Last 3 Months
</button>
<button mat-button (click)="applyQuick('B-1Y/E-1Y', 'last year')">
Last Year
</button>
</div>
</div>
</mat-tab>
<mat-tab label="Relative">
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'seconds', 59)"
>
<mat-label>Seconds:</mat-label>
<input
matInput
name="relative-time-range-seconds"
[(ngModel)]="relativeTimeRange.seconds"
type="number"
min="0"
max="59"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'minutes', 59)"
>
<mat-label>Minutes:</mat-label>
<input
matInput
name="relative-time-range-minutes"
[(ngModel)]="relativeTimeRange.minutes"
type="number"
min="0"
max="59"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'hours', 23)"
>
<mat-label>Hours:</mat-label>
<input
matInput
name="relative-time-range-hours"
[(ngModel)]="relativeTimeRange.hours"
type="number"
min="0"
max="23"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'days', 367)"
>
<mat-label>Days:</mat-label>
<input
matInput
name="relative-time-range-days"
[(ngModel)]="relativeTimeRange.days"
type="number"
min="0"
max="367"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'months', 11)"
>
<mat-label>Months:</mat-label>
<input
matInput
name="relative-time-range-months"
[(ngModel)]="relativeTimeRange.months"
type="number"
min="0"
max="11"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'years', 10)"
>
<mat-label>Years:</mat-label>
<input
matInput
name="relative-time-range-years"
[(ngModel)]="relativeTimeRange.years"
type="number"
min="0"
max="10"
/>
</mat-form-field>
<button mat-button (click)="applyRelativeTimeRange()">Apply</button>
</mat-tab>
<mat-tab label="Absolute">
<form [formGroup]="dateRangeFormGroup">
<mat-form-field class="date-picker-form-field">
<mat-label>Date Range:</mat-label>
<input
matInput
[formControl]="dateRangeFormGroup.controls.dateRange"
name="dates"
/>
</mat-form-field>
<button mat-button (click)="applyAbsoluteTime()">Apply</button>
</form>
</mat-tab>
</mat-tab-group>
</div>
</ng-template>

View File

@@ -0,0 +1,107 @@
import { Component } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
export type DateType = "quick" | "relative" | "absolute";
export type DateValue = { type: DateType; value: string; display: string };
@Component({
selector: "app-date-picker",
templateUrl: "./date-picker.component.html",
})
export class DatePickerComponent {
isOpen = false;
relativeTimeRangeUnit = "relativeTimeRangeMinutes";
relativeTimeRangeAmount = 15;
relativeTimeRange = {
seconds: 0,
minutes: 15,
hours: 0,
days: 0,
months: 0,
years: 0,
};
dateRangeFormGroup = new FormGroup({
dateRange: new FormControl<string>(
"2019-10-05 00:00:00 - 2019-10-11 23:59:59",
[
Validators.pattern(
/^\d{4}-\d{2}-\d{2} ([01][0-9]|2[0-3]):\d{2}:\d{2} - \d{4}-\d{2}-\d{2} ([01][0-9]|2[0-3]):\d{2}:\d{2}$/,
),
],
),
});
dateValue: DateValue = {
type: "quick",
value: "BM/EM",
display: "this month",
};
selectedTabIndex = 0;
constructor() {}
dateDisplay(): string {
return this.dateValue.value;
}
tabChange() {
//(<any> window).initSimpleDatePicker(); // breaks form control
}
applyQuick(value: string, display: string) {
this.dateValue.type = "quick";
this.dateValue.value = value;
this.dateValue.display = display;
this.isOpen = false;
}
private fixToRange(val: number, min: number, max: number) {
return val < min ? min : (val > max ? max : val);
}
applyRelativeTimeRange() {
const x = this.relativeTimeRange;
const years = x.years ? x.years + "Y" : "";
const months = x.months ? x.months + "M" : "";
const days = x.days ? x.days + "D" : "";
const time = x.hours || x.minutes || x.seconds ? "T" : "";
const hours = x.hours ? x.hours + "H" : "";
const minutes = x.minutes ? x.minutes + "M" : "";
const seconds = x.seconds ? x.seconds + "S" : "";
const isoTimeRange =
`P${years}${months}${days}${time}${hours}${minutes}${seconds}`;
this.dateValue.type = "relative";
this.dateValue.value = isoTimeRange;
this.dateValue.display = isoTimeRange;
this.isOpen = false;
}
applyAbsoluteTime() {
this.dateValue.type = "absolute";
this.dateValue.value = <string> this.dateRangeFormGroup.controls.dateRange
.value;
this.dateValue.display = <string> this.dateRangeFormGroup.controls.dateRange
.value;
this.isOpen = false;
console.log(this.dateValue);
}
scrollRelativeTimeRange(
event: WheelEvent,
unit: "seconds" | "minutes" | "hours" | "days" | "months" | "years",
max: number,
) {
this.relativeTimeRange[unit] = this.fixToRange(
this.relativeTimeRange[unit] + (event.deltaY > 0 ? -1 : 1),
0,
max,
);
}
}

View File

@@ -47,6 +47,22 @@
});
}
function initSimpleDatePicker() {
$('input[name="dates"]').daterangepicker({
timePicker: true,
minDate: "2017-01-01",
maxDate: "2029-12-31",
maxYear: parseInt(moment().format('YYYY'),10),
timePicker24Hour: true,
timePickerSeconds: true,
showDropdowns: true, // drop downs for selecting year and month
locale: {
format: 'YYYY-MM-DD HH:mm:ss',
"firstDay": 1 // monday is the first day of the week
},
});
}
$( document ).ready(function() {
initInvaders('results');
document.addEventListener("invadersPause", function(event) {

View File

@@ -182,6 +182,9 @@ a.external-link:after {
mat-form-field.pdb-form-full-width {
width: 100%;
}
mat-form-field.pdb-form-number-small {
width: 4.5em;
}
mat-form-field.pdb-form-number {
width: 5.5em;
}

View File

@@ -28,15 +28,10 @@ public class VariableByteEncoder {
public static final long MIN_VALUE = Long.MIN_VALUE / 2 + 1;
public static final long MAX_VALUE = Long.MAX_VALUE / 2;
private static final int MAX_BYTES_PER_VALUE = 10;
private static final int CONTINUATION_BYTE_FLAG = 1 << 7; // 10000000
private static final long DATA_BITS = (1 << 7) - 1; // 01111111
private static final ThreadLocal<byte[]> SINGLE_VALUE_BUFFER = ThreadLocal
.withInitial(() -> new byte[MAX_BYTES_PER_VALUE]);
/**
* Encodes time and value into the given buffer.
* <p>
@@ -137,8 +132,18 @@ public class VariableByteEncoder {
* input: 0 1 -1 2 -2 3 -3
* encoded: 1 2 3 4 5 6 7
* </pre>
*
* Note: 1. I tried to replace this ternary operator with a branchfree
* alternative, but it was slower:
*
* <pre>
* final long sign = (x - 1) >> 63; // will be 111...1 if first bit was 1 (x was negative) and 000..0
* // otherwise
* final long signBit = sign & 0x1; // will be 1 for negative x and 0 otherwise
* return (((x * 2) ^ (sign))) + (signBit * 2); // same as above, but branchless
* </pre>
*/
private static long encodeIntoPositiveValue(final long value) {
static long encodeIntoPositiveValue(final long value) {
return value > 0 ? value * 2 : (value * -2) + 1;
}
@@ -234,9 +239,9 @@ public class VariableByteEncoder {
}
public static int neededBytes(final long value) {
final byte[] buffer = SINGLE_VALUE_BUFFER.get();
final int usedBytes = encodeInto(value, buffer, 0);
return usedBytes;
final long val = encodeIntoPositiveValue(value);
final int numberOfOnes = 64 - Long.numberOfLeadingZeros(val);
return numberOfOnes / 7 + (numberOfOnes % 7 == 0 ? 0 : 1);
}
}

View File

@@ -81,7 +81,7 @@ public class VariableByteEncoderTest {
Assertions.assertEquals(originalValues, decodedValues);
}
public static Stream<Arguments> providerNededBytes() {
public static Stream<Arguments> providerNeededBytes() {
return Stream.of( //
Arguments.of(0, 1), //
Arguments.of(-10, 1), //
@@ -98,7 +98,7 @@ public class VariableByteEncoderTest {
}
@ParameterizedTest
@MethodSource("providerNededBytes")
@MethodSource("providerNeededBytes")
public void testNeededBytes(final long value, final int expectedNeededBytes) {
final int neededBytes = VariableByteEncoder.neededBytes(value);