@mwater/visualization 5.6.0 → 5.6.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 (92) hide show
  1. package/lib/ColorComponent.js +2 -2
  2. package/lib/TranslationsTabComponent.d.ts +34 -0
  3. package/lib/TranslationsTabComponent.js +256 -0
  4. package/lib/dashboards/DashboardComponent.js +1 -1
  5. package/lib/dashboards/ServerDashboardDataSource.d.ts +0 -1
  6. package/lib/dashboards/ServerDashboardDataSource.js +0 -15
  7. package/lib/dashboards/SettingsModalComponent.js +9 -233
  8. package/lib/datagrids/DatagridComponent.js +5 -0
  9. package/lib/datagrids/DatagridViewComponent.js +30 -4
  10. package/lib/maps/BufferLayer.d.ts +0 -13
  11. package/lib/maps/BufferLayer.js +12 -237
  12. package/lib/maps/BufferLayerDesignerComponent.d.ts +1 -1
  13. package/lib/maps/BufferLayerDesignerComponent.js +0 -5
  14. package/lib/maps/ChoroplethLayer.d.ts +1 -16
  15. package/lib/maps/ChoroplethLayer.js +13 -358
  16. package/lib/maps/ClusterLayer.d.ts +0 -9
  17. package/lib/maps/ClusterLayer.js +0 -250
  18. package/lib/maps/DirectMapDataSource.js +1 -38
  19. package/lib/maps/GridLayer.d.ts +0 -15
  20. package/lib/maps/GridLayer.js +0 -212
  21. package/lib/maps/Layer.d.ts +1 -26
  22. package/lib/maps/Layer.js +0 -13
  23. package/lib/maps/MapComponent.d.ts +19 -35
  24. package/lib/maps/MapComponent.js +135 -76
  25. package/lib/maps/MapControlComponent.d.ts +4 -5
  26. package/lib/maps/MapControlComponent.js +5 -12
  27. package/lib/maps/MapDesign.d.ts +8 -0
  28. package/lib/maps/MapDesignerComponent.d.ts +2 -0
  29. package/lib/maps/MapDesignerComponent.js +7 -2
  30. package/lib/maps/MapLayerDataSource.d.ts +0 -4
  31. package/lib/maps/MapLayerViewDesignerComponent.d.ts +3 -1
  32. package/lib/maps/MapLayerViewDesignerComponent.js +5 -1
  33. package/lib/maps/MapLayersDesignerComponent.d.ts +2 -0
  34. package/lib/maps/MapLayersDesignerComponent.js +2 -1
  35. package/lib/maps/MapTranslationsTab.d.ts +15 -0
  36. package/lib/maps/MapTranslationsTab.js +47 -0
  37. package/lib/maps/MapUtils.d.ts +11 -0
  38. package/lib/maps/MapUtils.js +47 -0
  39. package/lib/maps/MapViewComponent.d.ts +1 -1
  40. package/lib/maps/MapViewComponent.js +1 -8
  41. package/lib/maps/MarkersLayer.d.ts +1 -14
  42. package/lib/maps/MarkersLayer.js +71 -252
  43. package/lib/maps/MarkersLayerDesign.d.ts +4 -0
  44. package/lib/maps/MarkersLayerDesignerComponent.d.ts +20 -16
  45. package/lib/maps/MarkersLayerDesignerComponent.js +77 -23
  46. package/lib/maps/ServerMapDataSource.d.ts +0 -1
  47. package/lib/maps/ServerMapDataSource.js +0 -15
  48. package/lib/maps/SwitchableTileUrlLayer.d.ts +0 -2
  49. package/lib/maps/SwitchableTileUrlLayer.js +0 -9
  50. package/lib/maps/TileUrlLayer.d.ts +0 -1
  51. package/lib/maps/TileUrlLayer.js +0 -5
  52. package/lib/maps/VectorMapViewComponent.js +12 -1
  53. package/lib/maps/vectorMaps.d.ts +5 -6
  54. package/lib/maps/vectorMaps.js +13 -9
  55. package/lib/widgets/MapWidget.js +2 -1
  56. package/package.json +2 -2
  57. package/src/ColorComponent.tsx +2 -2
  58. package/src/TranslationsTabComponent.tsx +429 -0
  59. package/src/dashboards/DashboardComponent.tsx +1 -1
  60. package/src/dashboards/ServerDashboardDataSource.ts +0 -19
  61. package/src/dashboards/SettingsModalComponent.tsx +27 -383
  62. package/src/datagrids/DatagridComponent.tsx +6 -0
  63. package/src/datagrids/DatagridViewComponent.tsx +41 -5
  64. package/src/maps/BufferLayer.ts +16 -262
  65. package/src/maps/BufferLayerDesignerComponent.tsx +0 -6
  66. package/src/maps/ChoroplethLayer.ts +16 -393
  67. package/src/maps/ClusterLayer.ts +0 -274
  68. package/src/maps/DirectMapDataSource.ts +2 -49
  69. package/src/maps/GridLayer.ts +0 -224
  70. package/src/maps/Layer.ts +1 -35
  71. package/src/maps/MapComponent.tsx +448 -0
  72. package/src/maps/MapControlComponent.tsx +41 -0
  73. package/src/maps/MapDesign.ts +6 -0
  74. package/src/maps/MapDesignerComponent.tsx +18 -1
  75. package/src/maps/MapLayerDataSource.ts +0 -5
  76. package/src/maps/MapLayerViewDesignerComponent.ts +9 -2
  77. package/src/maps/MapLayersDesignerComponent.ts +4 -1
  78. package/src/maps/MapTranslationsTab.tsx +53 -0
  79. package/src/maps/MapUtils.ts +48 -0
  80. package/src/maps/MapViewComponent.tsx +2 -8
  81. package/src/maps/MarkersLayer.ts +79 -270
  82. package/src/maps/MarkersLayerDesign.ts +6 -0
  83. package/src/maps/MarkersLayerDesignerComponent.tsx +114 -38
  84. package/src/maps/ServerMapDataSource.ts +0 -19
  85. package/src/maps/SwitchableTileUrlLayer.tsx +0 -11
  86. package/src/maps/TileUrlLayer.tsx +0 -6
  87. package/src/maps/VectorMapViewComponent.tsx +13 -2
  88. package/src/maps/vectorMaps.tsx +12 -9
  89. package/src/widgets/MapWidget.tsx +2 -0
  90. package/src/maps/MapComponent.ts +0 -311
  91. package/src/maps/MapControlComponent.ts +0 -46
  92. package/src/maps/RasterMapViewComponent.ts +0 -345
@@ -29,7 +29,7 @@ import { getDefaultLayoutOptions } from "../dashboards/layoutOptions"
29
29
  import Widget from "../widgets/Widget"
30
30
  import BlocksLayoutManager from "../layouts/blocks/BlocksLayoutManager"
31
31
  import { getTranslatableStringsFromLayoutManager } from "../dashboards/DashboardUtils"
32
- import { getSimpleHoverOverData } from "./MapUtils"
32
+ import { getSimpleHoverOverData, getTranslatableStringsFromAxis, translateAxis } from "./MapUtils"
33
33
 
34
34
  export default class ChoroplethLayer extends Layer<ChoroplethLayerDesign> {
35
35
  /** Gets the type of layer definition */
@@ -837,389 +837,6 @@ export default class ChoroplethLayer extends Layer<ChoroplethLayerDesign> {
837
837
  }
838
838
  }
839
839
 
840
- /** Gets the layer definition as JsonQL + CSS in format:
841
- * {
842
- * layers: array of { id: layer id, jsonql: jsonql that includes "the_webmercator_geom" as a column }
843
- * css: carto css
844
- * interactivity: (optional) { layer: id of layer, fields: array of field names }
845
- * }
846
- * arguments:
847
- * design: design of layer
848
- * schema: schema to use
849
- * filters: array of filters to apply
850
- */
851
- getJsonQLCss(design: ChoroplethLayerDesign, schema: Schema, filters: JsonQLFilter[]): LayerDefinition {
852
- // Create design
853
- const layerDef = {
854
- layers: [{ id: "layer0", jsonql: this.createMapnikJsonQL(design, schema, filters) }],
855
- css: this.createCss(design, schema, filters),
856
- interactivity: {
857
- layer: "layer0",
858
- fields: ["id", "name"]
859
- }
860
- }
861
-
862
- return layerDef
863
- }
864
-
865
- createMapnikJsonQL(design: ChoroplethLayerDesign, schema: Schema, filters: JsonQLFilter[]): JsonQLQuery {
866
- const axisBuilder = new AxisBuilder({ schema })
867
- const exprCompiler = new ExprCompiler(schema)
868
-
869
- // Verify that scopeLevel is an integer to prevent injection
870
- if (design.scopeLevel != null && ![0, 1, 2, 3, 4, 5].includes(design.scopeLevel)) {
871
- throw new Error("Invalid scope level")
872
- }
873
-
874
- // Verify that detailLevel is an integer to prevent injection
875
- if (![0, 1, 2, 3, 4, 5].includes(design.detailLevel)) {
876
- throw new Error("Invalid detail level")
877
- }
878
-
879
- const regionsTable = design.regionsTable || "admin_regions"
880
-
881
- if (design.regionMode === "plain") {
882
- /*
883
- E.g:
884
- select name, shape_simplified from
885
- admin_regions as regions
886
- where regions.level0 = 'eb3e12a2-de1e-49a9-8afd-966eb55d47eb'
887
- and regions.level = 2
888
- */
889
- const query: JsonQLQuery = {
890
- type: "query",
891
- selects: [
892
- { type: "select", expr: { type: "field", tableAlias: "regions", column: "_id" }, alias: "id" },
893
- {
894
- type: "select",
895
- expr: { type: "field", tableAlias: "regions", column: "shape_simplified" },
896
- alias: "the_geom_webmercator"
897
- },
898
- { type: "select", expr: { type: "field", tableAlias: "regions", column: "name" }, alias: "name" }
899
- ],
900
- from: { type: "table", table: regionsTable, alias: "regions" },
901
- where: {
902
- type: "op",
903
- op: "and",
904
- exprs: [
905
- // Level to display
906
- {
907
- type: "op",
908
- op: "=",
909
- exprs: [{ type: "field", tableAlias: "regions", column: "level" }, design.detailLevel]
910
- }
911
- ]
912
- }
913
- }
914
-
915
- // Scope overall
916
- if (design.scope) {
917
- ; (query.where as JsonQLOp).exprs.push({
918
- type: "op",
919
- op: "=",
920
- exprs: [
921
- { type: "field", tableAlias: "regions", column: `level${design.scopeLevel || 0}` },
922
- { type: "literal", value: design.scope }
923
- ]
924
- })
925
- }
926
-
927
- // Add filters on regions to outer query
928
- for (const filter of filters) {
929
- if (filter.table == regionsTable) {
930
- ; (query.where as JsonQLOp).exprs.push(injectTableAlias(filter.jsonql, "regions"))
931
- }
932
- }
933
-
934
- return query
935
- }
936
-
937
- if (design.regionMode === "indirect" || !design.regionMode) {
938
- /*
939
- E.g:
940
- select name, shape_simplified, regions.color from
941
- admin_regions as regions2
942
- left outer join
943
- (
944
- select admin_regions.level2 as id,
945
- count(innerquery.*) as color
946
- from
947
- admin_regions inner join
948
- entities.water_point as innerquery
949
- on innerquery.admin_region = admin_regions._id
950
- where admin_regions.level0 = 'eb3e12a2-de1e-49a9-8afd-966eb55d47eb'
951
- group by 1
952
- ) as regions on regions.id = regions2._id
953
- where regions2.level = 2 and regions2.level0 = 'eb3e12a2-de1e-49a9-8afd-966eb55d47eb'
954
- */
955
- const compiledAdminRegionExpr = exprCompiler.compileExpr({
956
- expr: design.adminRegionExpr || null,
957
- tableAlias: "innerquery"
958
- })
959
-
960
- // Create inner query
961
- const innerQuery: JsonQLQuery = {
962
- type: "query",
963
- selects: [
964
- {
965
- type: "select",
966
- expr: { type: "field", tableAlias: "regions", column: `level${design.detailLevel}` },
967
- alias: "id"
968
- }
969
- ],
970
- from: {
971
- type: "join",
972
- kind: "inner",
973
- left: { type: "table", table: regionsTable, alias: "regions" },
974
- right: exprCompiler.compileTable(design.table!, "innerquery"),
975
- on: {
976
- type: "op",
977
- op: "=",
978
- exprs: [compiledAdminRegionExpr, { type: "field", tableAlias: "regions", column: "_id" }]
979
- }
980
- },
981
- groupBy: [1]
982
- }
983
-
984
- // Add color select if color axis
985
- if (design.axes.color) {
986
- const colorExpr = axisBuilder.compileAxis({ axis: design.axes.color, tableAlias: "innerquery" })
987
- innerQuery.selects.push({ type: "select", expr: colorExpr, alias: "color" })
988
- }
989
-
990
- // Add label select if color axis
991
- if (design.axes.label) {
992
- const labelExpr = axisBuilder.compileAxis({ axis: design.axes.label, tableAlias: "innerquery" })
993
- innerQuery.selects.push({ type: "select", expr: labelExpr, alias: "label" })
994
- }
995
-
996
- let whereClauses = []
997
-
998
- if (design.scope) {
999
- whereClauses.push({
1000
- type: "op",
1001
- op: "=",
1002
- exprs: [{ type: "field", tableAlias: "regions", column: `level${design.scopeLevel || 0}` }, design.scope]
1003
- })
1004
- }
1005
-
1006
- // Then add filters
1007
- if (design.filter) {
1008
- whereClauses.push(exprCompiler.compileExpr({ expr: design.filter, tableAlias: "innerquery" }))
1009
- }
1010
-
1011
- // Then add extra filters passed in, if relevant
1012
- const relevantFilters = _.where(filters, { table: design.table })
1013
- for (let filter of relevantFilters) {
1014
- whereClauses.push(injectTableAlias(filter.jsonql, "innerquery"))
1015
- }
1016
-
1017
- whereClauses = _.compact(whereClauses)
1018
-
1019
- if (whereClauses.length > 0) {
1020
- innerQuery.where = { type: "op", op: "and", exprs: whereClauses }
1021
- }
1022
-
1023
- // Now create outer query
1024
- const query: JsonQLQuery = {
1025
- type: "query",
1026
- selects: [
1027
- { type: "select", expr: { type: "field", tableAlias: "regions2", column: "_id" }, alias: "id" },
1028
- {
1029
- type: "select",
1030
- expr: { type: "field", tableAlias: "regions2", column: "shape_simplified" },
1031
- alias: "the_geom_webmercator"
1032
- },
1033
- { type: "select", expr: { type: "field", tableAlias: "regions2", column: "name" }, alias: "name" }
1034
- ],
1035
- from: {
1036
- type: "join",
1037
- kind: "left",
1038
- left: { type: "table", table: regionsTable, alias: "regions2" },
1039
- right: { type: "subquery", query: innerQuery, alias: "regions" },
1040
- on: {
1041
- type: "op",
1042
- op: "=",
1043
- exprs: [
1044
- { type: "field", tableAlias: "regions", column: "id" },
1045
- { type: "field", tableAlias: "regions2", column: "_id" }
1046
- ]
1047
- }
1048
- },
1049
- where: {
1050
- type: "op",
1051
- op: "and",
1052
- exprs: [
1053
- // Level to display
1054
- {
1055
- type: "op",
1056
- op: "=",
1057
- exprs: [{ type: "field", tableAlias: "regions2", column: "level" }, design.detailLevel]
1058
- }
1059
- ]
1060
- }
1061
- }
1062
-
1063
- // Scope overall
1064
- if (design.scope) {
1065
- ; (query.where as JsonQLOp).exprs.push({
1066
- type: "op",
1067
- op: "=",
1068
- exprs: [
1069
- { type: "field", tableAlias: "regions2", column: `level${design.scopeLevel || 0}` },
1070
- { type: "literal", value: design.scope }
1071
- ]
1072
- })
1073
- }
1074
-
1075
- // Add filters on regions to outer query
1076
- for (const filter of filters) {
1077
- if (filter.table == regionsTable) {
1078
- ; (query.where as JsonQLOp).exprs.push(injectTableAlias(filter.jsonql, "regions2"))
1079
- }
1080
- }
1081
-
1082
- // Bubble up color and label
1083
- if (design.axes.color) {
1084
- query.selects.push({
1085
- type: "select",
1086
- expr: { type: "field", tableAlias: "regions", column: "color" },
1087
- alias: "color"
1088
- })
1089
- }
1090
-
1091
- // Add label select if color axis
1092
- if (design.axes.label) {
1093
- query.selects.push({
1094
- type: "select",
1095
- expr: { type: "field", tableAlias: "regions", column: "label" },
1096
- alias: "label"
1097
- })
1098
- }
1099
-
1100
- return query
1101
- }
1102
-
1103
- if (design.regionMode === "direct") {
1104
- /*
1105
- E.g:
1106
- select name, shape_simplified from
1107
- admin_regions as regions
1108
- where regions.level0 = 'eb3e12a2-de1e-49a9-8afd-966eb55d47eb'
1109
- and regions.level = 2
1110
- */
1111
- const query: JsonQLQuery = {
1112
- type: "query",
1113
- selects: [
1114
- { type: "select", expr: { type: "field", tableAlias: "regions", column: "_id" }, alias: "id" },
1115
- {
1116
- type: "select",
1117
- expr: { type: "field", tableAlias: "regions", column: "shape_simplified" },
1118
- alias: "the_geom_webmercator"
1119
- },
1120
- { type: "select", expr: { type: "field", tableAlias: "regions", column: "name" }, alias: "name" }
1121
- ],
1122
- from: { type: "table", table: regionsTable, alias: "regions" },
1123
- where: {
1124
- type: "op",
1125
- op: "and",
1126
- exprs: [
1127
- // Level to display
1128
- {
1129
- type: "op",
1130
- op: "=",
1131
- exprs: [{ type: "field", tableAlias: "regions", column: "level" }, design.detailLevel]
1132
- }
1133
- ]
1134
- }
1135
- }
1136
-
1137
- // Add color select
1138
- if (design.axes.color) {
1139
- const colorExpr = axisBuilder.compileAxis({ axis: design.axes.color, tableAlias: "regions" })
1140
- query.selects.push({ type: "select", expr: colorExpr, alias: "color" })
1141
- }
1142
-
1143
- // Add label select if color axis
1144
- if (design.axes.label) {
1145
- const labelExpr = axisBuilder.compileAxis({ axis: design.axes.label, tableAlias: "regions" })
1146
- query.selects.push({ type: "select", expr: labelExpr, alias: "label" })
1147
- }
1148
-
1149
- // Scope overall
1150
- if (design.scope) {
1151
- ; (query.where as JsonQLOp).exprs.push({
1152
- type: "op",
1153
- op: "=",
1154
- exprs: [
1155
- { type: "field", tableAlias: "regions", column: `level${design.scopeLevel || 0}` },
1156
- { type: "literal", value: design.scope }
1157
- ]
1158
- })
1159
- }
1160
-
1161
- // Add filters on regions to outer query
1162
- for (const filter of filters) {
1163
- if (filter.table == regionsTable) {
1164
- ; (query.where as JsonQLOp).exprs.push(injectTableAlias(filter.jsonql, "regions"))
1165
- }
1166
- }
1167
-
1168
- return query
1169
- }
1170
-
1171
- throw new Error(`Unsupported regionMode ${design.regionMode}`)
1172
- }
1173
-
1174
- createCss(design: ChoroplethLayerDesign, schema: Schema, filters: JsonQLFilter[]): string {
1175
- let css =
1176
- `\
1177
- #layer0 {
1178
- line-color: ${design.borderColor || "#000"};
1179
- line-width: 1.5;
1180
- line-opacity: 0.5;
1181
- polygon-opacity: ` +
1182
- design.fillOpacity * design.fillOpacity +
1183
- `;
1184
- polygon-fill: ` +
1185
- (design.color || "transparent") +
1186
- `;
1187
- }
1188
- \
1189
- `
1190
-
1191
- if (design.displayNames) {
1192
- css += `\
1193
- #layer0::labels {
1194
- text-name: [name];
1195
- text-face-name: 'Arial Regular';
1196
- text-halo-radius: 2;
1197
- text-halo-opacity: 0.5;
1198
- text-halo-fill: #FFF;
1199
- }\
1200
- `
1201
- }
1202
-
1203
- // If color axes, add color conditions
1204
- if (design.axes.color && design.axes.color.colorMap) {
1205
- for (let item of design.axes.color.colorMap) {
1206
- // If invisible
1207
- if (_.includes(design.axes.color.excludedValues || [], item.value)) {
1208
- css += `#layer0 [color=${JSON.stringify(
1209
- item.value
1210
- )}] { line-color: transparent; polygon-opacity: 0; polygon-fill: transparent; }\n`
1211
- if (design.displayNames) {
1212
- css += `#layer0::labels [color=${JSON.stringify(item.value)}] { text-opacity: 0; text-halo-opacity: 0; }\n`
1213
- }
1214
- } else {
1215
- css += `#layer0 [color=${JSON.stringify(item.value)}] { polygon-fill: ${item.color}; }\n`
1216
- }
1217
- }
1218
- }
1219
-
1220
- return css
1221
- }
1222
-
1223
840
  /**
1224
841
  * Called when the interactivity grid is clicked.
1225
842
  * arguments:
@@ -1474,7 +1091,7 @@ export default class ChoroplethLayer extends Layer<ChoroplethLayerDesign> {
1474
1091
  // Get the legend to be optionally displayed on the map. Returns
1475
1092
  // a React element
1476
1093
  getLegend(options: LegendOptions<ChoroplethLayerDesign>): ReactNode {
1477
- const { design, schema, name, dataSource, locale, filters } = options
1094
+ const { design, schema, name, dataSource, locale, filters, translate } = options
1478
1095
  const _filters = filters.slice()
1479
1096
  if (design.filter != null) {
1480
1097
  const exprCompiler = new ExprCompiler(schema)
@@ -1489,17 +1106,19 @@ export default class ChoroplethLayer extends Layer<ChoroplethLayerDesign> {
1489
1106
  const regionsTable = design.regionsTable || "admin_regions"
1490
1107
  const axisTable = design.regionMode === "direct" ? regionsTable : design.table
1491
1108
 
1109
+ // Clean and translate axis
1110
+ const axis = translateAxis(axisBuilder.cleanAxis({
1111
+ axis: design.axes.color || null,
1112
+ table: axisTable,
1113
+ types: ["enum", "text", "boolean", "date"],
1114
+ aggrNeed: design.regionMode == "indirect" ? "required" : "none"
1115
+ }), translate)
1116
+
1492
1117
  return React.createElement(LayerLegendComponent, {
1493
1118
  schema,
1494
- name,
1119
+ name: translate(name),
1495
1120
  filters: _.compact(_filters),
1496
- axis:
1497
- axisBuilder.cleanAxis({
1498
- axis: design.axes.color || null,
1499
- table: axisTable,
1500
- types: ["enum", "text", "boolean", "date"],
1501
- aggrNeed: design.regionMode == "indirect" ? "required" : "none"
1502
- }) || undefined,
1121
+ axis: axis || undefined,
1503
1122
  defaultColor: design.color || undefined,
1504
1123
  locale
1505
1124
  })
@@ -1703,6 +1322,10 @@ export default class ChoroplethLayer extends Layer<ChoroplethLayerDesign> {
1703
1322
  getTranslatableStrings(design: ChoroplethLayerDesign, schema: Schema): string[] {
1704
1323
  const strings: string[] = []
1705
1324
 
1325
+ // Add strings from axis category labels and null labels
1326
+ strings.push(...getTranslatableStringsFromAxis(design.axes.color))
1327
+ strings.push(...getTranslatableStringsFromAxis(design.axes.label))
1328
+
1706
1329
  // Add strings from hoverOver items
1707
1330
  if (design.hoverOver && design.hoverOver.items) {
1708
1331
  for (const item of design.hoverOver.items) {