x4js 2.3.28 → 2.3.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/aicontext.md +380 -19
  2. package/package.json +7 -3
  3. package/src/components/bargraph/bargraph.module.scss +44 -0
  4. package/src/components/bargraph/bargraph.ts +362 -0
  5. package/src/components/base.scss +71 -1
  6. package/src/components/calendar/calendar.module.scss +6 -5
  7. package/src/components/calendar/calendar.ts +14 -8
  8. package/src/components/canvas/canvas.ts +5 -4
  9. package/src/components/carousel/carousel.module.scss +165 -0
  10. package/src/components/carousel/carousel.ts +423 -0
  11. package/src/components/chart/chart.module.scss +8 -0
  12. package/src/components/chart/chart.ts +170 -12
  13. package/src/components/combobox/combobox.module.scss +2 -1
  14. package/src/components/components.ts +3 -0
  15. package/src/components/gantt/gantt.module.scss +462 -0
  16. package/src/components/gantt/gantt.ts +1254 -0
  17. package/src/components/knob/knob.module.scss +113 -0
  18. package/src/components/knob/knob.ts +355 -0
  19. package/src/components/mapview/mapview.module.scss +177 -0
  20. package/src/components/mapview/mapview.ts +764 -0
  21. package/src/components/planner/planner.module.scss +688 -0
  22. package/src/components/planner/planner.ts +1792 -0
  23. package/src/components/radio/radio.module.scss +4 -1
  24. package/src/components/slider/slider.module.scss +22 -20
  25. package/src/components/switch/switch.module.scss +68 -44
  26. package/src/components/switch/switch.ts +6 -3
  27. package/src/components/tag/tag.module.scss +4 -4
  28. package/src/components/textedit/textedit.module.scss +5 -2
  29. package/src/components/themes.scss +19 -6
  30. package/src/core/core_i18n.ts +65 -0
  31. package/src/core/core_tools.ts +44 -0
  32. package/src/x4doc.ts +3 -0
@@ -21,7 +21,7 @@ import { clamp, formatNumber, IRect, isNumber, sanitizeHtml } from '../../core/c
21
21
 
22
22
  import "./chart.module.scss"
23
23
 
24
- export type ChartType = "line" | "area" | "bar" | "pie" | "donut";
24
+ export type ChartType = "line" | "area" | "bar" | "pie" | "donut" | "radar";
25
25
 
26
26
  export interface ChartTick {
27
27
  value: number; // on x axis: index of the point
@@ -38,9 +38,9 @@ export interface ChartAxis {
38
38
 
39
39
  export interface ChartSerie {
40
40
  name?: string;
41
- values: number[]; // undefined: no value (hole in the line)
41
+ values: number[]; // undefined: no value (hole in the line; at the center of a radar)
42
42
  color?: string; // default: palette --chart-color-1..8
43
- fill?: string; // area fill color
43
+ fill?: string; // area fill color ("none" on a radar: the line only)
44
44
  dots?: boolean; // always show the points
45
45
  }
46
46
 
@@ -57,7 +57,7 @@ interface ChartEvents extends ComponentEvents {
57
57
  export interface ChartProps extends Omit<SvgProps,"svg"|"viewbox"|"content"> {
58
58
  type?: ChartType; // default "line"
59
59
  series: ChartSerie[]; // pie & donut use the first serie
60
- labels?: string[]; // default: 1, 2, 3...
60
+ labels?: string[]; // default: 1, 2, 3...; radar: the name of each axis
61
61
  xaxis?: ChartAxis;
62
62
  yaxis?: ChartAxis;
63
63
  legend?: boolean; // default: shown when there is more than one named serie
@@ -211,7 +211,10 @@ function drawBar( grp: SvgGroup, x: number, w: number, y0: number, y1: number, r
211
211
 
212
212
 
213
213
  /**
214
- * Minimal chart (line, area, bar, pie, donut) drawn in svg.
214
+ * Minimal chart (line, area, bar, pie, donut, radar) drawn in svg.
215
+ *
216
+ * radar: an axis for each point, from the center (yaxis.min, 0 by default) to the outer ring
217
+ * (yaxis.max); each serie is a closed line, tinted inside. At least 3 points.
215
218
  *
216
219
  * @example
217
220
  * new Chart( {
@@ -335,6 +338,11 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
335
338
  * make an element clickable with a tooltip
336
339
  */
337
340
 
341
+ private _tip( serie: number, index: number, value: number ) {
342
+ const name = this._series[serie].name ? ` · ${this._series[serie].name}` : '';
343
+ return `${this._label(index)}${name}: ${this._format(value)}`;
344
+ }
345
+
338
346
  private _mark<T extends SvgShape | SvgPath>( item: T, serie: number, index: number, tip: string ): T {
339
347
  item.setAttr( "data-serie", serie+"" )
340
348
  .setAttr( "data-index", index+"" )
@@ -381,6 +389,9 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
381
389
  if( pie ) {
382
390
  this._drawPie( bld, area );
383
391
  }
392
+ else if( this._type=="radar" ) {
393
+ this._drawRadar( bld, area );
394
+ }
384
395
  else {
385
396
  this._drawXY( bld, area );
386
397
  }
@@ -648,11 +659,6 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
648
659
  grp.clip( id );
649
660
  }
650
661
 
651
- const tip = ( s: number, i: number, v: number ) => {
652
- const name = series[s].name ? ` · ${series[s].name}` : '';
653
- return `${this._label(i)}${name}: ${this._format(v)}`;
654
- }
655
-
656
662
  if( band ) {
657
663
  const n = stacked ? 1 : Math.max( 1, series.length );
658
664
  const w = Math.min( xstep*0.8/n, MAX_BAR );
@@ -684,7 +690,7 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
684
690
  }
685
691
 
686
692
  this._paint( bar.addClass( "bar" ), s, serie.color );
687
- this._mark( bar, s, i, tip( s, i, v ) );
693
+ this._mark( bar, s, i, this._tip( s, i, v ) );
688
694
  }
689
695
  });
690
696
  }
@@ -757,11 +763,163 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
757
763
  for( const p of pts ) {
758
764
  const dot = dots.circle( p.x, p.y, 4 ).addClass( visible ? "dot" : "dot ghost" );
759
765
  this._paint( dot, s, serie.color );
760
- this._mark( dot, s, p.i, tip( s, p.i, value( s, p.i ) ?? 0 ) );
766
+ this._mark( dot, s, p.i, this._tip( s, p.i, value( s, p.i ) ?? 0 ) );
761
767
  }
762
768
  }
763
769
  }
764
770
  });
765
771
  }
766
772
  }
773
+
774
+ /**
775
+ * radar: an axis for each point, a ring for each tick of the y scale
776
+ */
777
+
778
+ private _drawRadar( bld: SvgBuilder, area: IRect ) {
779
+
780
+ const series = this._series;
781
+ const xaxis = this._xaxis;
782
+ const yaxis = this._yaxis;
783
+
784
+ // number of axes
785
+ let count = xaxis.max ?? 0;
786
+ if( xaxis.max===undefined ) {
787
+ series.forEach( s => { count = Math.max( count, s.values?.length ?? 0 ); } );
788
+ }
789
+
790
+ // less than 3 axes make no surface
791
+ if( count<3 ) {
792
+ return;
793
+ }
794
+
795
+ const value = ( s: number, i: number ) => {
796
+ const v = series[s].values?.[i];
797
+ return isNumber(v) ? v : undefined;
798
+ }
799
+
800
+ // room for the labels around
801
+ let maxw = 0;
802
+ for( let i=0; i<count; i++ ) {
803
+ maxw = Math.max( maxw, textWidth( this._label(i) ) );
804
+ }
805
+
806
+ const cx = area.left + area.width/2;
807
+ const cy = area.top + area.height/2;
808
+ const radius = Math.min( area.width/2 - maxw - 8, area.height/2 - 16 );
809
+
810
+ if( radius<=10 ) {
811
+ return;
812
+ }
813
+
814
+ // scale: the center at 0, lower for negative values
815
+ let dmin = 0;
816
+ let dmax = -Infinity;
817
+
818
+ series.forEach( ( _, s ) => {
819
+ for( let i=0; i<count; i++ ) {
820
+ const v = value( s, i );
821
+ if( v!==undefined ) {
822
+ dmin = Math.min( dmin, v );
823
+ dmax = Math.max( dmax, v );
824
+ }
825
+ }
826
+ });
827
+
828
+ if( dmax==-Infinity ) {
829
+ dmax = 1;
830
+ }
831
+
832
+ const nice = niceScale( yaxis.min ?? dmin, yaxis.max ?? dmax, clamp( Math.floor( radius/30 ), 2, 6 ) );
833
+ const min = yaxis.min ?? nice.min;
834
+ let max = yaxis.max ?? nice.max;
835
+ if( max<=min ) {
836
+ max = min+1;
837
+ }
838
+
839
+ const step = yaxis.step>0 ? yaxis.step : nice.step;
840
+ const rings = ( yaxis.ticks ?? makeTicks( min, max, step, v => this._format(v) ) ).filter( t => t.value>min && t.value<=max );
841
+
842
+ // the axis i: 0 at the top, clockwise; a value out of the bounds stays on them
843
+ const angle = ( i: number ) => ( i/count*360 - 90 ) * Math.PI/180;
844
+ const point = ( i: number, v: number ) => {
845
+ const r = ( clamp( v, min, max ) - min ) / ( max-min ) * radius;
846
+ return { x: cx + r*Math.cos( angle(i) ), y: cy + r*Math.sin( angle(i) ) };
847
+ }
848
+
849
+ const polygon = ( grp: SvgGroup, v: ( i: number ) => number ) => {
850
+ const pth = grp.path( );
851
+ for( let i=0; i<count; i++ ) {
852
+ const p = point( i, v(i) );
853
+ if( i==0 ) {
854
+ pth.moveTo( p.x, p.y );
855
+ }
856
+ else {
857
+ pth.lineTo( p.x, p.y );
858
+ }
859
+ }
860
+
861
+ return pth.closePath( );
862
+ }
863
+
864
+ // grid & axes
865
+ const grid = bld.group( ).addClass( "axis radar" );
866
+
867
+ for( const t of rings ) {
868
+ polygon( grid, ( ) => t.value ).addClass( "grid" );
869
+ }
870
+
871
+ for( let i=0; i<count; i++ ) {
872
+ const p = point( i, max );
873
+ grid.path( ).moveTo( cx, cy ).lineTo( p.x, p.y ).addClass( "base" );
874
+
875
+ // the label beyond the end of its axis, on the side of the axis
876
+ const cos = Math.cos( angle(i) );
877
+ const sin = Math.sin( angle(i) );
878
+ const halign = Math.abs( cos )<0.3 ? 'center' : ( cos>0 ? 'left' : 'right' );
879
+ const valign = Math.abs( sin )<0.3 ? 'center' : ( sin>0 ? 'top' : 'bottom' );
880
+ drawText( grid, cx + ( radius+8 )*cos, cy + ( radius+8 )*sin, this._label(i), halign, valign );
881
+ }
882
+
883
+ // the values of the rings, along the first axis
884
+ for( const t of rings ) {
885
+ drawText( grid, cx+4, point( 0, t.value ).y, t.text, 'left', 'center' );
886
+ }
887
+
888
+ // series
889
+ const grp = bld.group( ).addClass( "plot" );
890
+ const areas = grp.group( );
891
+ const lines = grp.group( );
892
+ const dots = grp.group( );
893
+
894
+ const ghosts = series.length*count<=MAX_GHOSTS;
895
+
896
+ series.forEach( ( serie, s ) => {
897
+ // a value missing is at the center
898
+ const at = ( i: number ) => value( s, i ) ?? min;
899
+
900
+ const pth = polygon( areas, at ).addClass( "area" );
901
+ this._paint( pth, s, serie.color );
902
+
903
+ if( serie.fill ) {
904
+ pth.setStyle( "fill", serie.fill ).setStyle( "fillOpacity", "1" );
905
+ }
906
+
907
+ this._paint( polygon( lines, at ).addClass( "line" ), s, serie.color );
908
+
909
+ // points: always visible, or only on hover (ghost)
910
+ if( serie.dots || ghosts ) {
911
+ for( let i=0; i<count; i++ ) {
912
+ const v = value( s, i );
913
+ if( v===undefined ) {
914
+ continue;
915
+ }
916
+
917
+ const p = point( i, v );
918
+ const dot = dots.circle( p.x, p.y, 4 ).addClass( serie.dots ? "dot" : "dot ghost" );
919
+ this._paint( dot, s, serie.color );
920
+ this._mark( dot, s, i, this._tip( s, i, v ) );
921
+ }
922
+ }
923
+ });
924
+ }
767
925
  }
@@ -23,7 +23,7 @@
23
23
  --combobox-border: var( --border );
24
24
  --combobox-border-focus: var( --border-focus );
25
25
 
26
- --combobox-btn-background: inherit;
26
+ --combobox-btn-background: var( --background-input ); // the one of its field: inherit was the page (dark theme)
27
27
  --combobox-btn-color: var( --text-secondary );
28
28
  --combobox-btn-color-hover: var( --text-primary );
29
29
 
@@ -110,6 +110,7 @@
110
110
  margin: 0;
111
111
  padding: 0;
112
112
  outline: none;
113
+ align-self: stretch; // the height of the field: no strip of the page above and under it
113
114
  background-color: var( --combobox-btn-background );
114
115
  color: var( --combobox-btn-color );
115
116
 
@@ -1,4 +1,5 @@
1
1
  export * from "./autocomplete/autocomplete"
2
+ export * from "./bargraph/bargraph"
2
3
  export * from "./boxes/boxes"
3
4
  export * from "./breadcrumb/breadcrumb"
4
5
  export * from "./btngroup/btngroup"
@@ -6,6 +7,7 @@ export * from "./button/button"
6
7
  export * from "./calendar/calendar"
7
8
  export * from "./canvas/canvas"
8
9
  export * from "./canvas/canvas_ex"
10
+ export * from "./carousel/carousel"
9
11
  export * from "./chart/chart"
10
12
  export * from "./checkbox/checkbox"
11
13
  export * from "./colorinput/colorinput"
@@ -22,6 +24,7 @@ export * from "./icon/icon"
22
24
  export * from "./image/image"
23
25
  export * from "./input/input"
24
26
  export * from "./keyboard/keyboard"
27
+ export * from "./knob/knob"
25
28
  export * from "./label/label"
26
29
  export * from "./link/link"
27
30
  export * from "./listbox/listbox"