@mwater/visualization 5.0.0 → 5.0.1

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 (48) hide show
  1. package/lib/MWaterContextComponent.js +1 -4
  2. package/lib/datagrids/DatagridComponent.d.ts +2 -0
  3. package/lib/datagrids/DatagridComponent.js +8 -2
  4. package/lib/datagrids/DatagridViewComponent.d.ts +2 -0
  5. package/lib/datagrids/DatagridViewComponent.js +3 -2
  6. package/lib/datagrids/LabeledExprGenerator.js +15 -0
  7. package/lib/dayjs.d.ts +2 -0
  8. package/lib/dayjs.js +9 -0
  9. package/lib/languages.js +5 -0
  10. package/lib/maps/DetailLevelSelectComponent.d.ts +1 -93
  11. package/lib/maps/Layer.js +7 -18
  12. package/lib/maps/MapComponent.js +1 -1
  13. package/lib/maps/RegionSelectComponent.d.ts +1 -33
  14. package/lib/maps/VectorMapViewComponent.js +21 -29
  15. package/lib/quickfilter/QuickfiltersComponent.d.ts +2 -186
  16. package/lib/quickfilter/QuickfiltersDesignComponent.js +1 -1
  17. package/lib/quickfilter/TextLiteralComponent.d.ts +2 -186
  18. package/lib/quickfilter/TextLiteralComponent.js +3 -0
  19. package/lib/valueFormatter.js +52 -1
  20. package/lib/widgets/charts/layered/LayeredChartCompiler.d.ts +1 -1
  21. package/lib/widgets/charts/pivot/PivotChartLayout.d.ts +3 -2
  22. package/lib/widgets/charts/pivot/PivotChartLayoutComponent.js +4 -1
  23. package/lib/widgets/charts/pivot/PivotChartQueryBuilder.js +1 -1
  24. package/lib/widgets/charts/table/TableChart.js +15 -4
  25. package/lib/widgets/charts/table/TableChartViewComponent.d.ts +2 -1
  26. package/lib/widgets/charts/table/TableChartViewComponent.js +9 -4
  27. package/package.json +8 -8
  28. package/src/MWaterAddRelatedIndicatorComponent.ts +1 -1
  29. package/src/MWaterContextComponent.ts +1 -4
  30. package/src/datagrids/DatagridComponent.ts +15 -1
  31. package/src/datagrids/DatagridViewComponent.ts +6 -2
  32. package/src/datagrids/LabeledExprGenerator.ts +15 -0
  33. package/src/dayjs.ts +5 -0
  34. package/src/languages.ts +5 -0
  35. package/src/maps/Layer.ts +6 -16
  36. package/src/maps/MapComponent.ts +1 -1
  37. package/src/maps/RasterMapViewComponent.ts +0 -1
  38. package/src/maps/VectorMapViewComponent.tsx +23 -36
  39. package/src/quickfilter/QuickfiltersDesignComponent.tsx +1 -1
  40. package/src/quickfilter/TextLiteralComponent.ts +4 -0
  41. package/src/valueFormatter.ts +54 -1
  42. package/src/widgets/charts/pivot/PivotChartLayout.ts +3 -2
  43. package/src/widgets/charts/pivot/PivotChartLayoutBuilder.ts +2 -2
  44. package/src/widgets/charts/pivot/PivotChartLayoutComponent.tsx +7 -3
  45. package/src/widgets/charts/pivot/PivotChartQueryBuilder.ts +1 -1
  46. package/src/widgets/charts/table/TableChart.ts +24 -14
  47. package/src/widgets/charts/table/TableChartViewComponent.ts +10 -5
  48. package/stories/dashboards.js +3 -3
@@ -7,7 +7,6 @@ const prop_types_1 = __importDefault(require("prop-types"));
7
7
  const lodash_1 = __importDefault(require("lodash"));
8
8
  const react_1 = __importDefault(require("react"));
9
9
  const R = react_1.default.createElement;
10
- const moment_1 = __importDefault(require("moment"));
11
10
  const react_linkify_1 = __importDefault(require("react-linkify"));
12
11
  const AxisBuilder_1 = __importDefault(require("../../../axes/AxisBuilder"));
13
12
  const expressions_1 = require("@mwater/expressions");
@@ -149,7 +148,13 @@ class TableContentsComponent extends react_1.default.Component {
149
148
  else {
150
149
  // Parse if should be JSON
151
150
  if (["image", "imagelist", "geometry", "text[]"].includes(exprType || "") && lodash_1.default.isString(value)) {
152
- value = JSON.parse(value);
151
+ try {
152
+ value = JSON.parse(value);
153
+ }
154
+ catch (e) {
155
+ // Ignore as can happen when re-arranging columns
156
+ value = null;
157
+ }
153
158
  }
154
159
  if (column.backgroundColorAxis && row[`bc${columnIndex}`] != null) {
155
160
  backgroundColor = axisBuilder.getValueColor(column.backgroundColorAxis, row[`bc${columnIndex}`]) ?? "#fff";
@@ -170,10 +175,10 @@ class TableContentsComponent extends react_1.default.Component {
170
175
  node = exprUtils.stringifyExprLiteral(column.textAxis?.expr, value, this.context.locale);
171
176
  break;
172
177
  case "date":
173
- node = (0, moment_1.default)(value, "YYYY-MM-DD").format("ll");
178
+ node = (0, valueFormatter_1.formatValue)(exprType, value, column.format);
174
179
  break;
175
180
  case "datetime":
176
- node = (0, moment_1.default)(value, moment_1.default.ISO_8601).format("lll");
181
+ node = (0, valueFormatter_1.formatValue)(exprType, value, column.format);
177
182
  break;
178
183
  case "image":
179
184
  node = this.renderImage(value.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mwater/visualization",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "Visualization library",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -20,6 +20,11 @@
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
22
  "@babel/runtime": "^7.12.5",
23
+ "@mwater/expressions": "^2.49.3",
24
+ "@mwater/expressions-ui": "^4.13.0",
25
+ "@mwater/forms": "^11.2.7",
26
+ "@mwater/jsonql": "^1.14.0",
27
+ "@mwater/react-library": "^2.0.0",
23
28
  "@turf/bbox": "^6.0.1",
24
29
  "async": "^1.2.1",
25
30
  "async-latest": "^1.0.0",
@@ -34,6 +39,7 @@
34
39
  "d3-scale": "^1.0.3",
35
40
  "d3-scale-chromatic": "^1.5.0",
36
41
  "d3-tip": "^0.9.0",
42
+ "dayjs": "^1.11.10",
37
43
  "dompurify": "^1.0.11",
38
44
  "ez-localize": "^2.5.0",
39
45
  "file-saver": "^2.0.5",
@@ -42,16 +48,12 @@
42
48
  "immutable-setter": "^1.1.1",
43
49
  "jquery": "^3.6.3",
44
50
  "js-yaml": "^3.14.0",
45
- "@mwater/jsonql": "^1.14.0",
46
51
  "leaflet": "^1.8.0",
47
52
  "lodash": "^3.1.0",
48
53
  "lru-cache": "^6.0.0",
49
54
  "maplibre-gl": "^3.3.1",
50
55
  "markdown-it": "^12.0.4",
51
56
  "moment": "^2.29.1",
52
- "@mwater/expressions": "^2.49.3",
53
- "@mwater/expressions-ui": "^4.13.0",
54
- "@mwater/forms": "^11.2.7",
55
57
  "pako": "^1.0.11",
56
58
  "prop-types": "^15.7.2",
57
59
  "querystring": "^0.2.0",
@@ -65,14 +67,12 @@
65
67
  "react-dropzone": "^14.2.2",
66
68
  "react-float-affixed": "^1.0.0",
67
69
  "react-lazy-load-image-component": "^1.5.1",
68
- "@mwater/react-library": "^2.0.0",
69
70
  "react-linkify": "^0.2.2",
70
71
  "react-motion": "^0.5.2",
71
72
  "react-onclickout": "^2.0.4",
72
73
  "react-select": "^5.1.0",
73
74
  "save-svg-as-png": "^1.4.17",
74
75
  "shallowequal": "^0.2.2",
75
- "ui-builder": "^2.0.1",
76
76
  "update-object": "^1.0.0",
77
77
  "utm": "^1.1.1",
78
78
  "uuid": "^3.4.0"
@@ -108,6 +108,6 @@
108
108
  "react-addons-test-utils": "^0.14.0",
109
109
  "react-test-renderer": "^16.14.0",
110
110
  "sinon": "^4.2.0",
111
- "typescript": "^4.8.4"
111
+ "typescript": "^5.2.2"
112
112
  }
113
113
  }
@@ -43,7 +43,7 @@ export default class MWaterAddRelatedIndicatorComponent extends React.Component<
43
43
 
44
44
  componentDidMount() {
45
45
  // Get all response-type indicators
46
- const query = {}
46
+ const query: any = {}
47
47
  query.selector = JSON.stringify({ type: "response" })
48
48
  query.fields = JSON.stringify({
49
49
  "design.name": 1,
@@ -122,11 +122,8 @@ export default class MWaterContextComponent extends React.Component<{
122
122
  return null
123
123
  }
124
124
 
125
- // Always open indicator section
125
+ // Nothing initially open
126
126
  context.isScalarExprTreeSectionInitiallyOpen = (options: any) => {
127
- if (options.tableId.match(/^entities\./) && options.section.id === "!indicators") {
128
- return true
129
- }
130
127
  return null
131
128
  }
132
129
 
@@ -68,6 +68,7 @@ export default class DatagridComponent extends React.Component<
68
68
  /** Height of quickfilters */
69
69
  quickfiltersHeight: number | null
70
70
  quickfiltersValues: null | any[]
71
+ refreshKey: number
71
72
  }
72
73
  > {
73
74
  datagridView?: DatagridViewComponent | null
@@ -81,7 +82,8 @@ export default class DatagridComponent extends React.Component<
81
82
  editingDesign: false, // is design being edited
82
83
  cellEditingEnabled: false, // True if cells can be edited directly
83
84
  quickfiltersHeight: null, // Height of quickfilters
84
- quickfiltersValues: null
85
+ quickfiltersValues: null,
86
+ refreshKey: 1
85
87
  }
86
88
  }
87
89
 
@@ -97,6 +99,11 @@ export default class DatagridComponent extends React.Component<
97
99
  return this.updateHeight()
98
100
  }
99
101
 
102
+ handleRefreshData = () => {
103
+ this.props.dataSource.clearCache?.()
104
+ this.setState({ refreshKey: this.state.refreshKey + 1 })
105
+ }
106
+
100
107
  updateHeight() {
101
108
  // Calculate quickfilters height
102
109
  if (this.quickfilters) {
@@ -261,6 +268,12 @@ export default class DatagridComponent extends React.Component<
261
268
  this.renderFindReplace(),
262
269
  this.renderCellEdit(),
263
270
  this.renderEditButton(),
271
+ R(
272
+ "a",
273
+ { key: "refresh", className: "btn btn-link btn-sm", onClick: this.handleRefreshData },
274
+ R("span", { className: "fas fa-sync" }),
275
+ R("span", { className: "hide-600px" }, " Refresh")
276
+ ),
264
277
  this.props.extraTitleButtonsElem
265
278
  ),
266
279
  this.props.titleElem
@@ -375,6 +388,7 @@ export default class DatagridComponent extends React.Component<
375
388
  onRowDoubleClick: this.props.onRowDoubleClick,
376
389
  canEditExpr: this.state.cellEditingEnabled ? this.props.canEditExpr : undefined,
377
390
  updateExprValues: this.state.cellEditingEnabled ? this.props.updateExprValues : undefined,
391
+ refreshKey: this.state.refreshKey
378
392
  })
379
393
  } else if (this.props.onDesignChange) {
380
394
  return R(
@@ -38,6 +38,9 @@ export interface DatagridViewComponentProps {
38
38
 
39
39
  /** Called when a row is clicked with (tableId, rowId, rowIndex) */
40
40
  onRowClick?: (tableId: string, rowId: any, rowIndex: number) => void
41
+
42
+ /** Change to force a refresh */
43
+ refreshKey?: any
41
44
  }
42
45
 
43
46
  /** Update to one row expression value */
@@ -91,7 +94,7 @@ export default class DatagridViewComponent extends React.Component<
91
94
  componentWillReceiveProps(nextProps: DatagridViewComponentProps) {
92
95
  // If design or filters changed, delete all rows
93
96
  // TODO won't this reload on column resize?
94
- if (!_.isEqual(nextProps.design, this.props.design) || !_.isEqual(nextProps.filters, this.props.filters)) {
97
+ if (!_.isEqual(nextProps.design, this.props.design) || !_.isEqual(nextProps.filters, this.props.filters) || nextProps.refreshKey !== this.props.refreshKey) {
95
98
  return this.setState({ rows: [], entirelyLoaded: false })
96
99
  }
97
100
  }
@@ -452,7 +455,8 @@ export default class DatagridViewComponent extends React.Component<
452
455
  onRowDoubleClick: this.handleRowDoubleClick,
453
456
  onRowClick: this.handleRowClick,
454
457
  isColumnResizing: false,
455
- onColumnResizeEndCallback: this.handleColumnResize
458
+ onColumnResizeEndCallback: this.handleColumnResize,
459
+ touchScrollEnabled: true
456
460
  },
457
461
  this.renderColumns()
458
462
  )
@@ -140,6 +140,21 @@ export default class LabeledExprGenerator {
140
140
  joins
141
141
  }
142
142
  ]
143
+ } else if (column.idTable!.match(/^custom./)) { // Support cascading ref question
144
+ return this.schema
145
+ .getColumns(column.idTable!)
146
+ .filter((c: any) => c.id[0] !== "_")
147
+ .map((c: any) => ({
148
+ expr: {
149
+ type: "scalar",
150
+ table,
151
+ joins: [column.id],
152
+ expr: { type: "field", table: column.idTable, column: c.id }
153
+ },
154
+
155
+ label: `${createLabel(column)} > ${createLabel(c)}`,
156
+ joins
157
+ }))
143
158
  } else {
144
159
  // Use label, code, full name, or name of dest table
145
160
  const destTable = this.schema.getTable(column.idTable!)
package/src/dayjs.ts ADDED
@@ -0,0 +1,5 @@
1
+ import dayjs from "dayjs"
2
+ import localizedFormat from 'dayjs/plugin/localizedFormat'
3
+ dayjs.extend(localizedFormat)
4
+
5
+ export default dayjs
package/src/languages.ts CHANGED
@@ -513,6 +513,11 @@ export const languages: {
513
513
  "name": "Māori",
514
514
  "en": "Maori"
515
515
  },
516
+ {
517
+ "code": "miq",
518
+ "name": "Miskitu",
519
+ "en": "Miskito"
520
+ },
516
521
  {
517
522
  "code": "mk",
518
523
  "name": "Македонски",
package/src/maps/Layer.ts CHANGED
@@ -312,22 +312,12 @@ export default class Layer<LayerDesign> {
312
312
 
313
313
  if (results[0].bounds) {
314
314
  const [w, s, e, n] = bbox(results[0].bounds)
315
- // Pad to 10km if point
316
- if (w === e && n === s) {
317
- bounds = {
318
- w: w - 0.1,
319
- s: s - 0.1,
320
- e: e + 0.1,
321
- n: n + 0.1
322
- }
323
- // Pad bounds to prevent too small box (10m)
324
- } else {
325
- bounds = {
326
- w: w - 0.001,
327
- s: s - 0.001,
328
- e: e + 0.001,
329
- n: n + 0.001
330
- }
315
+ // Pad bounds to prevent too small box (100m)
316
+ bounds = {
317
+ w: w - 0.001,
318
+ s: s - 0.001,
319
+ e: e + 0.001,
320
+ n: n + 0.001
331
321
  }
332
322
  }
333
323
 
@@ -303,7 +303,7 @@ export default class MapComponent extends React.Component<MapComponentProps, Map
303
303
  gridTemplateAreas: `"header designer" "quickfilters designer" "view designer"`
304
304
  }
305
305
  },
306
- this.renderHeader(),
306
+ this.props.hideTitleBar != true ? this.renderHeader() : null,
307
307
  this.state.hideQuickfilters ? null : this.renderQuickfilter(),
308
308
  R("div", { style: { width: "100%", height: "100%", gridArea: "view", overflow: "hidden" } }, this.renderView()),
309
309
  designerVisible ? this.renderDesigner() : null
@@ -13,7 +13,6 @@ import { JsonQLFilter } from "../JsonQLFilter"
13
13
  import { MapDesign } from "./MapDesign"
14
14
  import { MapDataSource } from "./MapDataSource"
15
15
  import { MapScope } from "./MapUtils"
16
- import { defaultProps } from "react-select/src/Select"
17
16
 
18
17
  export interface RasterMapViewComponentProps {
19
18
  schema: Schema
@@ -1,4 +1,4 @@
1
- import _ from "lodash"
1
+ import _, { find } from "lodash"
2
2
  import { LayerSpecification, MapLayerMouseEvent } from "maplibre-gl"
3
3
  import { DataSource, Schema } from "@mwater/expressions"
4
4
  import React, { CSSProperties, ReactNode, useEffect, useMemo, useState } from "react"
@@ -14,7 +14,7 @@ import {
14
14
  getFilterableTables as utilsGetFilterableTables,
15
15
  MapScope
16
16
  } from "./MapUtils"
17
- import { memoizedDebounce } from "../memoizedDebounce"
17
+ import {Color} from '@maplibre/maplibre-gl-style-spec';
18
18
 
19
19
  import "maplibre-gl/dist/maplibre-gl.css"
20
20
  import "./VectorMapViewComponent.css"
@@ -115,7 +115,7 @@ export function VectorMapViewComponent(props: {
115
115
  )
116
116
 
117
117
  // Last feature that mouse entered
118
- const lastFeature = useRef()
118
+ const lastFeature = useRef<string>()
119
119
 
120
120
  // Load map
121
121
  const map = useVectorMap({
@@ -162,6 +162,7 @@ export function VectorMapViewComponent(props: {
162
162
  })
163
163
 
164
164
  if (!results) {
165
+ setHoverContents(null)
165
166
  return
166
167
  }
167
168
 
@@ -171,15 +172,6 @@ export function VectorMapViewComponent(props: {
171
172
  }
172
173
  })
173
174
 
174
- const handleLayerHoverDebounced = memoizedDebounce(
175
- (layerViewId: string, ev: { data: any; event: any }) => {
176
- handleLayerHover(layerViewId, ev)
177
- },
178
- 250,
179
- { leading: true, trailing: false },
180
- (layerViewId, ev) => ev.data.id
181
- )
182
-
183
175
  /** Handle a click on a layer */
184
176
  const handleLayerClick = useStableCallback((layerViewId: string, ev: { data: any; event: any }) => {
185
177
  const layerView = props.design.layerViews.find(lv => lv.id == layerViewId)!
@@ -514,7 +506,7 @@ export function VectorMapViewComponent(props: {
514
506
  setBusy(b => b - 1)
515
507
 
516
508
  if (bounds) {
517
- map!.fitBounds([bounds.w, bounds.s, bounds.e, bounds.n], { padding: 20, duration: 3000 })
509
+ map!.fitBounds([bounds.w, bounds.s, bounds.e, bounds.n], { padding: 20, duration: 2500 })
518
510
 
519
511
  // Also record if editable as part of bounds
520
512
  setBounds(bounds)
@@ -598,42 +590,37 @@ export function VectorMapViewComponent(props: {
598
590
  return
599
591
  }
600
592
 
601
- const removes: { (): void }[] = []
602
593
 
603
- for (const clickHandler of layerClickHandlers) {
604
- const onEnter = (ev: MapLayerMouseEvent) => {
605
- if (!ev.features || !ev.features[0].properties) {
594
+ const layers = layerClickHandlers.map(clickHandler => clickHandler.mapLayerId)
595
+
596
+ const onEnter = (ev: MapLayerMouseEvent) => {
597
+ let f = map.queryRenderedFeatures(ev.point, { layers });
598
+ if (f.length) {
599
+ if(f[0].layer.type === 'fill' && f[0].layer.paint?.['fill-color']?.toString() == 'rgba(0,0,0,0)') {
606
600
  lastFeature.current = undefined
607
601
  setHoverContents(null)
608
602
  return
609
603
  }
610
-
611
- if (ev.features![0].properties.id !== lastFeature.current) {
604
+ if(lastFeature.current !== f[0].properties.id) {
605
+ lastFeature.current = f[0].properties.id
612
606
  setHoverContents(null)
613
- lastFeature.current = ev.features![0].properties.id
607
+ const handler = find(layerClickHandlers, {mapLayerId: f[0].layer.id})
608
+ handleLayerHover(handler!.layerViewId, {
609
+ data: f[0].properties,
610
+ event: ev
611
+ })
614
612
  }
615
-
616
- handleLayerHoverDebounced(clickHandler.layerViewId, {
617
- data: ev.features![0].properties,
618
- event: ev
619
- })
620
- }
621
- const onLeave = (ev: MapLayerMouseEvent) => {
613
+ } else {
622
614
  lastFeature.current = undefined
623
615
  setHoverContents(null)
624
616
  }
625
- map.on("mousemove", clickHandler.mapLayerId, onEnter)
626
- map.on("mouseleave", clickHandler.mapLayerId, onLeave)
627
- removes.push(() => {
628
- map.off("mousemove", clickHandler.mapLayerId, onEnter)
629
- map.off("mouseleave", clickHandler.mapLayerId, onLeave)
630
- })
631
617
  }
618
+ map.on("mousemove", onEnter)
619
+
632
620
  return () => {
633
- for (const remove of removes) {
634
- remove()
635
- }
621
+ map.off("mousemove", onEnter)
636
622
  }
623
+
637
624
  }, [map, layerClickHandlers])
638
625
 
639
626
  function renderLegend() {
@@ -210,7 +210,7 @@ class QuickfilterDesignComponent extends React.Component<
210
210
  table: this.state.table,
211
211
  value: this.props.design.expr,
212
212
  onChange: this.handleExprChange,
213
- types: ["enum", "text", "enumset", "date", "datetime", "id[]", "text[]"]
213
+ types: ["enum", "text", "enumset", "date", "datetime", "id[]", "text[]"],
214
214
  })
215
215
  )
216
216
  ),
@@ -51,6 +51,10 @@ export default class TextLiteralComponent extends React.Component<TextLiteralCom
51
51
  // Create query to get matches
52
52
  const exprCompiler = new ExprCompiler(this.props.schema)
53
53
 
54
+ if(!this.props.expr.table) {
55
+ return
56
+ }
57
+
54
58
  // Add filter for input (simple if just text)
55
59
  if (exprType === "text") {
56
60
  if (input) {
@@ -1,6 +1,7 @@
1
1
  import { LiteralType } from "@mwater/expressions"
2
2
  import { format as d3Format } from "d3-format"
3
3
  import { fromLatLon } from "utm"
4
+ import dayjs from './dayjs'
4
5
 
5
6
  /** Option for list of format options */
6
7
  export interface FormatOption {
@@ -10,7 +11,7 @@ export interface FormatOption {
10
11
 
11
12
  /** Determine if can format type */
12
13
  export function canFormatType(type: LiteralType): boolean {
13
- return type == "number" || type == "geometry"
14
+ return type == "number" || type == "geometry" || type === "date" || type === "datetime"
14
15
  }
15
16
 
16
17
  /** Get available options for formatting a type. Null if not available */
@@ -35,6 +36,43 @@ export function getFormatOptions(type: LiteralType): FormatOption[] | null {
35
36
  ]
36
37
  }
37
38
 
39
+ if (type == "date") {
40
+ return [
41
+ { value: "ll", label: dayjs().format("ll") },
42
+ { value: "YYYY-MM-DD", label: dayjs().format("YYYY-MM-DD") },
43
+ { value: "YYYY-MMM-DD", label: dayjs().format("YYYY-MMM-DD") },
44
+ { value: "YYYY-MM", label: dayjs().format("YYYY-MM") },
45
+ { value: "YYYY", label: dayjs().format("YYYY") },
46
+ { value: "MMM YYYY", label: dayjs().format("MMM YYYY") },
47
+ { value: "MMM DD, YYYY", label: dayjs().format("MMM DD, YYYY") },
48
+ { value: "MMMM D, YYYY", label: dayjs().format("MMMM D, YYYY") },
49
+ { value: "MM/DD/YYYY", label: dayjs().format("MM/DD/YYYY") },
50
+ { value: "DD/MM/YYYY", label: dayjs().format("DD/MM/YYYY") },
51
+ { value: "DD-MM-YYYY", label: dayjs().format("DD-MM-YYYY") },
52
+ ]
53
+ }
54
+
55
+ if (type == "datetime") {
56
+ return [
57
+ { value: "lll", label: dayjs().format("lll") },
58
+ { value: "llll", label: dayjs().format("llll") },
59
+ { value: "YYYY-MM-DD", label: dayjs().format("YYYY-MM-DD") },
60
+ { value: "YYYY-MMM-DD", label: dayjs().format("YYYY-MMM-DD") },
61
+ { value: "YYYY-MM", label: dayjs().format("YYYY-MM") },
62
+ { value: "YYYY", label: dayjs().format("YYYY") },
63
+ { value: "MMM YYYY", label: dayjs().format("MMM YYYY") },
64
+ { value: "MMM DD, YYYY h:mm A", label: dayjs().format("MMM DD, YYYY h:mm A") },
65
+ { value: "MMMM D, YYYY h:mm A", label: dayjs().format("MMMM D, YYYY h:mm A") },
66
+ { value: "MMM DD, YYYY", label: dayjs().format("MMM DD, YYYY") },
67
+ { value: "MMMM D, YYYY", label: dayjs().format("MMMM D, YYYY") },
68
+ { value: "MM/DD/YYYY", label: dayjs().format("MM/DD/YYYY") },
69
+ { value: "DD/MM/YYYY", label: dayjs().format("DD/MM/YYYY") },
70
+ { value: "DD-MM-YYYY", label: dayjs().format("DD-MM-YYYY") },
71
+ { value: "YYYY-MM-DD HH:mm:ss", label: dayjs().format("YYYY-MM-DD HH:mm:ss") },
72
+ { value: "ISO 8601", label: dayjs().toISOString() },
73
+ ]
74
+ }
75
+
38
76
  return null
39
77
  }
40
78
 
@@ -48,6 +86,14 @@ export function getDefaultFormat(type: LiteralType): string {
48
86
  return "lat, lng"
49
87
  }
50
88
 
89
+ if (type == "date") {
90
+ return "ll"
91
+ }
92
+
93
+ if (type == "datetime") {
94
+ return "lll"
95
+ }
96
+
51
97
  throw new Error("Not supported")
52
98
  }
53
99
 
@@ -78,6 +124,13 @@ export function formatValue(
78
124
  return d3Format(format)(value)
79
125
  }
80
126
 
127
+ if (["date", "datetime"].includes(type)) {
128
+ if (format == "ISO 8601") {
129
+ return dayjs(value).toISOString()
130
+ }
131
+ return dayjs(value).format(format)
132
+ }
133
+
81
134
  if (type == "geometry") {
82
135
  if (format == "UTM") {
83
136
  if (value.type == "Point") {
@@ -40,8 +40,9 @@ export interface PivotChartLayoutCell {
40
40
  align: "left" | "center" | "right"
41
41
 
42
42
  /** section id of either a segment or intersection or blank area. Always a rectangle.
43
- * Id is like intersection id if intersection, id of segment if segment */
44
- section: string
43
+ * Id is like intersection id if intersection, id of segment if segment. Undefined if doesn't
44
+ * exist yet. */
45
+ section?: string
45
46
 
46
47
  /** true if cell is on top edge of section */
47
48
  sectionTop?: boolean
@@ -373,7 +373,7 @@ export default class PivotChartLayoutBuilder {
373
373
  const intersectionData = dataIndexed[intersectionId]
374
374
 
375
375
  // Create key to lookup value
376
- const key = {}
376
+ const key: { [rNcN: string]: any } = {}
377
377
  for (i = 0; i < row.length; i++) {
378
378
  part = row[i]
379
379
  key[`r${i}`] = part.value
@@ -727,7 +727,7 @@ export default class PivotChartLayoutBuilder {
727
727
  // Sort categories if segment is sorted
728
728
  if (segment.orderExpr) {
729
729
  // Index the ordering by the JSON.stringify to make it O(n)
730
- const orderIndex = {}
730
+ const orderIndex: { [key: string]: number } = {}
731
731
  const iterable = _.pluck(data[segment.id], "value")
732
732
  for (let index = 0; index < iterable.length; index++) {
733
733
  value = iterable[index]
@@ -5,7 +5,7 @@ const R = React.createElement
5
5
  import Color from "color"
6
6
  import * as ui from "@mwater/react-library/lib/bootstrap"
7
7
  import classNames from "classnames"
8
- import { PivotChartLayout, PivotChartLayoutRow } from "./PivotChartLayout"
8
+ import { PivotChartLayout, PivotChartLayoutCell, PivotChartLayoutRow } from "./PivotChartLayout"
9
9
 
10
10
  export interface PivotChartLayoutComponentProps {
11
11
  layout: PivotChartLayout
@@ -71,7 +71,7 @@ export default class PivotChartLayoutComponent extends React.Component<PivotChar
71
71
  layout: this.props.layout,
72
72
  rowIndex,
73
73
  columnIndex,
74
- onHover: this.props.editable ? () => this.setState({ hoverSection: cell.section }) : undefined,
74
+ onHover: this.props.editable ? () => this.setState({ hoverSection: cell.section ?? null }) : undefined,
75
75
  hoverSection: this.props.editable ? this.state.hoverSection : undefined,
76
76
  onEditSection: this.props.onEditSection ? this.props.onEditSection.bind(null, cell.section) : undefined,
77
77
  onSummarizeSegment: this.props.onSummarizeSegment
@@ -383,7 +383,11 @@ class LayoutCellComponent extends React.Component<LayoutCellComponentProps> {
383
383
  }
384
384
 
385
385
  // Render an unconfigured cell
386
- renderUnconfigured(cell: any) {
386
+ renderUnconfigured(cell: PivotChartLayoutCell) {
387
+ if (!cell.section) {
388
+ return null
389
+ }
390
+
387
391
  return R(
388
392
  "span",
389
393
  { style: { fontSize: "90%" } },
@@ -108,7 +108,7 @@ export default class PivotChartQueryBuilder {
108
108
  const columnSegment = columnPath[i]
109
109
  query.selects.push({
110
110
  type: "select",
111
- expr: this.axisBuilder.compileAxis({ axis: columnSegment.valueAxis, tableAlias: "main" }),
111
+ expr: compileSegmentAxis(columnSegment.valueAxis),
112
112
  alias: `c${i}`
113
113
  })
114
114
  query.groupBy!.push(i + 1 + rowPath.length)