@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
@@ -767,356 +767,6 @@ class ChoroplethLayer extends Layer_1.default {
767
767
  maxZoom: design.maxZoom
768
768
  };
769
769
  }
770
- /** Gets the layer definition as JsonQL + CSS in format:
771
- * {
772
- * layers: array of { id: layer id, jsonql: jsonql that includes "the_webmercator_geom" as a column }
773
- * css: carto css
774
- * interactivity: (optional) { layer: id of layer, fields: array of field names }
775
- * }
776
- * arguments:
777
- * design: design of layer
778
- * schema: schema to use
779
- * filters: array of filters to apply
780
- */
781
- getJsonQLCss(design, schema, filters) {
782
- // Create design
783
- const layerDef = {
784
- layers: [{ id: "layer0", jsonql: this.createMapnikJsonQL(design, schema, filters) }],
785
- css: this.createCss(design, schema, filters),
786
- interactivity: {
787
- layer: "layer0",
788
- fields: ["id", "name"]
789
- }
790
- };
791
- return layerDef;
792
- }
793
- createMapnikJsonQL(design, schema, filters) {
794
- const axisBuilder = new AxisBuilder_1.default({ schema });
795
- const exprCompiler = new expressions_1.ExprCompiler(schema);
796
- // Verify that scopeLevel is an integer to prevent injection
797
- if (design.scopeLevel != null && ![0, 1, 2, 3, 4, 5].includes(design.scopeLevel)) {
798
- throw new Error("Invalid scope level");
799
- }
800
- // Verify that detailLevel is an integer to prevent injection
801
- if (![0, 1, 2, 3, 4, 5].includes(design.detailLevel)) {
802
- throw new Error("Invalid detail level");
803
- }
804
- const regionsTable = design.regionsTable || "admin_regions";
805
- if (design.regionMode === "plain") {
806
- /*
807
- E.g:
808
- select name, shape_simplified from
809
- admin_regions as regions
810
- where regions.level0 = 'eb3e12a2-de1e-49a9-8afd-966eb55d47eb'
811
- and regions.level = 2
812
- */
813
- const query = {
814
- type: "query",
815
- selects: [
816
- { type: "select", expr: { type: "field", tableAlias: "regions", column: "_id" }, alias: "id" },
817
- {
818
- type: "select",
819
- expr: { type: "field", tableAlias: "regions", column: "shape_simplified" },
820
- alias: "the_geom_webmercator"
821
- },
822
- { type: "select", expr: { type: "field", tableAlias: "regions", column: "name" }, alias: "name" }
823
- ],
824
- from: { type: "table", table: regionsTable, alias: "regions" },
825
- where: {
826
- type: "op",
827
- op: "and",
828
- exprs: [
829
- // Level to display
830
- {
831
- type: "op",
832
- op: "=",
833
- exprs: [{ type: "field", tableAlias: "regions", column: "level" }, design.detailLevel]
834
- }
835
- ]
836
- }
837
- };
838
- // Scope overall
839
- if (design.scope) {
840
- ;
841
- query.where.exprs.push({
842
- type: "op",
843
- op: "=",
844
- exprs: [
845
- { type: "field", tableAlias: "regions", column: `level${design.scopeLevel || 0}` },
846
- { type: "literal", value: design.scope }
847
- ]
848
- });
849
- }
850
- // Add filters on regions to outer query
851
- for (const filter of filters) {
852
- if (filter.table == regionsTable) {
853
- ;
854
- query.where.exprs.push((0, expressions_1.injectTableAlias)(filter.jsonql, "regions"));
855
- }
856
- }
857
- return query;
858
- }
859
- if (design.regionMode === "indirect" || !design.regionMode) {
860
- /*
861
- E.g:
862
- select name, shape_simplified, regions.color from
863
- admin_regions as regions2
864
- left outer join
865
- (
866
- select admin_regions.level2 as id,
867
- count(innerquery.*) as color
868
- from
869
- admin_regions inner join
870
- entities.water_point as innerquery
871
- on innerquery.admin_region = admin_regions._id
872
- where admin_regions.level0 = 'eb3e12a2-de1e-49a9-8afd-966eb55d47eb'
873
- group by 1
874
- ) as regions on regions.id = regions2._id
875
- where regions2.level = 2 and regions2.level0 = 'eb3e12a2-de1e-49a9-8afd-966eb55d47eb'
876
- */
877
- const compiledAdminRegionExpr = exprCompiler.compileExpr({
878
- expr: design.adminRegionExpr || null,
879
- tableAlias: "innerquery"
880
- });
881
- // Create inner query
882
- const innerQuery = {
883
- type: "query",
884
- selects: [
885
- {
886
- type: "select",
887
- expr: { type: "field", tableAlias: "regions", column: `level${design.detailLevel}` },
888
- alias: "id"
889
- }
890
- ],
891
- from: {
892
- type: "join",
893
- kind: "inner",
894
- left: { type: "table", table: regionsTable, alias: "regions" },
895
- right: exprCompiler.compileTable(design.table, "innerquery"),
896
- on: {
897
- type: "op",
898
- op: "=",
899
- exprs: [compiledAdminRegionExpr, { type: "field", tableAlias: "regions", column: "_id" }]
900
- }
901
- },
902
- groupBy: [1]
903
- };
904
- // Add color select if color axis
905
- if (design.axes.color) {
906
- const colorExpr = axisBuilder.compileAxis({ axis: design.axes.color, tableAlias: "innerquery" });
907
- innerQuery.selects.push({ type: "select", expr: colorExpr, alias: "color" });
908
- }
909
- // Add label select if color axis
910
- if (design.axes.label) {
911
- const labelExpr = axisBuilder.compileAxis({ axis: design.axes.label, tableAlias: "innerquery" });
912
- innerQuery.selects.push({ type: "select", expr: labelExpr, alias: "label" });
913
- }
914
- let whereClauses = [];
915
- if (design.scope) {
916
- whereClauses.push({
917
- type: "op",
918
- op: "=",
919
- exprs: [{ type: "field", tableAlias: "regions", column: `level${design.scopeLevel || 0}` }, design.scope]
920
- });
921
- }
922
- // Then add filters
923
- if (design.filter) {
924
- whereClauses.push(exprCompiler.compileExpr({ expr: design.filter, tableAlias: "innerquery" }));
925
- }
926
- // Then add extra filters passed in, if relevant
927
- const relevantFilters = lodash_1.default.where(filters, { table: design.table });
928
- for (let filter of relevantFilters) {
929
- whereClauses.push((0, expressions_1.injectTableAlias)(filter.jsonql, "innerquery"));
930
- }
931
- whereClauses = lodash_1.default.compact(whereClauses);
932
- if (whereClauses.length > 0) {
933
- innerQuery.where = { type: "op", op: "and", exprs: whereClauses };
934
- }
935
- // Now create outer query
936
- const query = {
937
- type: "query",
938
- selects: [
939
- { type: "select", expr: { type: "field", tableAlias: "regions2", column: "_id" }, alias: "id" },
940
- {
941
- type: "select",
942
- expr: { type: "field", tableAlias: "regions2", column: "shape_simplified" },
943
- alias: "the_geom_webmercator"
944
- },
945
- { type: "select", expr: { type: "field", tableAlias: "regions2", column: "name" }, alias: "name" }
946
- ],
947
- from: {
948
- type: "join",
949
- kind: "left",
950
- left: { type: "table", table: regionsTable, alias: "regions2" },
951
- right: { type: "subquery", query: innerQuery, alias: "regions" },
952
- on: {
953
- type: "op",
954
- op: "=",
955
- exprs: [
956
- { type: "field", tableAlias: "regions", column: "id" },
957
- { type: "field", tableAlias: "regions2", column: "_id" }
958
- ]
959
- }
960
- },
961
- where: {
962
- type: "op",
963
- op: "and",
964
- exprs: [
965
- // Level to display
966
- {
967
- type: "op",
968
- op: "=",
969
- exprs: [{ type: "field", tableAlias: "regions2", column: "level" }, design.detailLevel]
970
- }
971
- ]
972
- }
973
- };
974
- // Scope overall
975
- if (design.scope) {
976
- ;
977
- query.where.exprs.push({
978
- type: "op",
979
- op: "=",
980
- exprs: [
981
- { type: "field", tableAlias: "regions2", column: `level${design.scopeLevel || 0}` },
982
- { type: "literal", value: design.scope }
983
- ]
984
- });
985
- }
986
- // Add filters on regions to outer query
987
- for (const filter of filters) {
988
- if (filter.table == regionsTable) {
989
- ;
990
- query.where.exprs.push((0, expressions_1.injectTableAlias)(filter.jsonql, "regions2"));
991
- }
992
- }
993
- // Bubble up color and label
994
- if (design.axes.color) {
995
- query.selects.push({
996
- type: "select",
997
- expr: { type: "field", tableAlias: "regions", column: "color" },
998
- alias: "color"
999
- });
1000
- }
1001
- // Add label select if color axis
1002
- if (design.axes.label) {
1003
- query.selects.push({
1004
- type: "select",
1005
- expr: { type: "field", tableAlias: "regions", column: "label" },
1006
- alias: "label"
1007
- });
1008
- }
1009
- return query;
1010
- }
1011
- if (design.regionMode === "direct") {
1012
- /*
1013
- E.g:
1014
- select name, shape_simplified from
1015
- admin_regions as regions
1016
- where regions.level0 = 'eb3e12a2-de1e-49a9-8afd-966eb55d47eb'
1017
- and regions.level = 2
1018
- */
1019
- const query = {
1020
- type: "query",
1021
- selects: [
1022
- { type: "select", expr: { type: "field", tableAlias: "regions", column: "_id" }, alias: "id" },
1023
- {
1024
- type: "select",
1025
- expr: { type: "field", tableAlias: "regions", column: "shape_simplified" },
1026
- alias: "the_geom_webmercator"
1027
- },
1028
- { type: "select", expr: { type: "field", tableAlias: "regions", column: "name" }, alias: "name" }
1029
- ],
1030
- from: { type: "table", table: regionsTable, alias: "regions" },
1031
- where: {
1032
- type: "op",
1033
- op: "and",
1034
- exprs: [
1035
- // Level to display
1036
- {
1037
- type: "op",
1038
- op: "=",
1039
- exprs: [{ type: "field", tableAlias: "regions", column: "level" }, design.detailLevel]
1040
- }
1041
- ]
1042
- }
1043
- };
1044
- // Add color select
1045
- if (design.axes.color) {
1046
- const colorExpr = axisBuilder.compileAxis({ axis: design.axes.color, tableAlias: "regions" });
1047
- query.selects.push({ type: "select", expr: colorExpr, alias: "color" });
1048
- }
1049
- // Add label select if color axis
1050
- if (design.axes.label) {
1051
- const labelExpr = axisBuilder.compileAxis({ axis: design.axes.label, tableAlias: "regions" });
1052
- query.selects.push({ type: "select", expr: labelExpr, alias: "label" });
1053
- }
1054
- // Scope overall
1055
- if (design.scope) {
1056
- ;
1057
- query.where.exprs.push({
1058
- type: "op",
1059
- op: "=",
1060
- exprs: [
1061
- { type: "field", tableAlias: "regions", column: `level${design.scopeLevel || 0}` },
1062
- { type: "literal", value: design.scope }
1063
- ]
1064
- });
1065
- }
1066
- // Add filters on regions to outer query
1067
- for (const filter of filters) {
1068
- if (filter.table == regionsTable) {
1069
- ;
1070
- query.where.exprs.push((0, expressions_1.injectTableAlias)(filter.jsonql, "regions"));
1071
- }
1072
- }
1073
- return query;
1074
- }
1075
- throw new Error(`Unsupported regionMode ${design.regionMode}`);
1076
- }
1077
- createCss(design, schema, filters) {
1078
- let css = `\
1079
- #layer0 {
1080
- line-color: ${design.borderColor || "#000"};
1081
- line-width: 1.5;
1082
- line-opacity: 0.5;
1083
- polygon-opacity: ` +
1084
- design.fillOpacity * design.fillOpacity +
1085
- `;
1086
- polygon-fill: ` +
1087
- (design.color || "transparent") +
1088
- `;
1089
- }
1090
- \
1091
- `;
1092
- if (design.displayNames) {
1093
- css += `\
1094
- #layer0::labels {
1095
- text-name: [name];
1096
- text-face-name: 'Arial Regular';
1097
- text-halo-radius: 2;
1098
- text-halo-opacity: 0.5;
1099
- text-halo-fill: #FFF;
1100
- }\
1101
- `;
1102
- }
1103
- // If color axes, add color conditions
1104
- if (design.axes.color && design.axes.color.colorMap) {
1105
- for (let item of design.axes.color.colorMap) {
1106
- // If invisible
1107
- if (lodash_1.default.includes(design.axes.color.excludedValues || [], item.value)) {
1108
- css += `#layer0 [color=${JSON.stringify(item.value)}] { line-color: transparent; polygon-opacity: 0; polygon-fill: transparent; }\n`;
1109
- if (design.displayNames) {
1110
- css += `#layer0::labels [color=${JSON.stringify(item.value)}] { text-opacity: 0; text-halo-opacity: 0; }\n`;
1111
- }
1112
- }
1113
- else {
1114
- css += `#layer0 [color=${JSON.stringify(item.value)}] { polygon-fill: ${item.color}; }\n`;
1115
- }
1116
- }
1117
- }
1118
- return css;
1119
- }
1120
770
  /**
1121
771
  * Called when the interactivity grid is clicked.
1122
772
  * arguments:
@@ -1320,7 +970,7 @@ class ChoroplethLayer extends Layer_1.default {
1320
970
  // Get the legend to be optionally displayed on the map. Returns
1321
971
  // a React element
1322
972
  getLegend(options) {
1323
- const { design, schema, name, dataSource, locale, filters } = options;
973
+ const { design, schema, name, dataSource, locale, filters, translate } = options;
1324
974
  const _filters = filters.slice();
1325
975
  if (design.filter != null) {
1326
976
  const exprCompiler = new expressions_1.ExprCompiler(schema);
@@ -1333,16 +983,18 @@ class ChoroplethLayer extends Layer_1.default {
1333
983
  const axisBuilder = new AxisBuilder_1.default({ schema });
1334
984
  const regionsTable = design.regionsTable || "admin_regions";
1335
985
  const axisTable = design.regionMode === "direct" ? regionsTable : design.table;
986
+ // Clean and translate axis
987
+ const axis = (0, MapUtils_1.translateAxis)(axisBuilder.cleanAxis({
988
+ axis: design.axes.color || null,
989
+ table: axisTable,
990
+ types: ["enum", "text", "boolean", "date"],
991
+ aggrNeed: design.regionMode == "indirect" ? "required" : "none"
992
+ }), translate);
1336
993
  return react_1.default.createElement(LayerLegendComponent_1.default, {
1337
994
  schema,
1338
- name,
995
+ name: translate(name),
1339
996
  filters: lodash_1.default.compact(_filters),
1340
- axis: axisBuilder.cleanAxis({
1341
- axis: design.axes.color || null,
1342
- table: axisTable,
1343
- types: ["enum", "text", "boolean", "date"],
1344
- aggrNeed: design.regionMode == "indirect" ? "required" : "none"
1345
- }) || undefined,
997
+ axis: axis || undefined,
1346
998
  defaultColor: design.color || undefined,
1347
999
  locale
1348
1000
  });
@@ -1518,6 +1170,9 @@ class ChoroplethLayer extends Layer_1.default {
1518
1170
  /** Get strings to be translated */
1519
1171
  getTranslatableStrings(design, schema) {
1520
1172
  const strings = [];
1173
+ // Add strings from axis category labels and null labels
1174
+ strings.push(...(0, MapUtils_1.getTranslatableStringsFromAxis)(design.axes.color));
1175
+ strings.push(...(0, MapUtils_1.getTranslatableStringsFromAxis)(design.axes.label));
1521
1176
  // Add strings from hoverOver items
1522
1177
  if (design.hoverOver && design.hoverOver.items) {
1523
1178
  for (const item of design.hoverOver.items) {
@@ -9,15 +9,6 @@ export default class ClusterLayer extends Layer<ClusterLayerDesign> {
9
9
  getLayerDefinitionType(): "VectorTile";
10
10
  getVectorTile(design: ClusterLayerDesign, sourceId: string, schema: Schema, filters: JsonQLFilter[], opacity: number): VectorTileDef;
11
11
  createJsonQL(design: ClusterLayerDesign, schema: Schema, filters: JsonQLFilter[]): JsonQLQuery;
12
- getJsonQLCss(design: ClusterLayerDesign, schema: Schema, filters: JsonQLFilter[]): {
13
- layers: {
14
- id: string;
15
- jsonql: JsonQLQuery;
16
- }[];
17
- css: string;
18
- };
19
- createMapnikJsonQL(design: ClusterLayerDesign, schema: Schema, filters: JsonQLFilter[]): JsonQLQuery;
20
- createCss(design: ClusterLayerDesign, schema: Schema): string;
21
12
  getBounds(design: ClusterLayerDesign, schema: Schema, dataSource: DataSource, filters: JsonQLFilter[], callback: any): void;
22
13
  getMinZoom(design: ClusterLayerDesign): number | undefined;
23
14
  getMaxZoom(design: ClusterLayerDesign): number;
@@ -295,256 +295,6 @@ class ClusterLayer extends Layer_1.default {
295
295
  };
296
296
  return query;
297
297
  }
298
- // Gets the layer definition as JsonQL + CSS in format:
299
- // {
300
- // layers: array of { id: layer id, jsonql: jsonql that includes "the_webmercator_geom" as a column }
301
- // css: carto css
302
- // interactivity: (optional) { layer: id of layer, fields: array of field names }
303
- // }
304
- // arguments:
305
- // design: design of layer
306
- // schema: schema to use
307
- // filters: array of filters to apply. Each is { table: table id, jsonql: jsonql condition with {alias} for tableAlias. Use injectAlias to put in table alias
308
- getJsonQLCss(design, schema, filters) {
309
- // Create design
310
- const layerDef = {
311
- layers: [{ id: "layer0", jsonql: this.createMapnikJsonQL(design, schema, filters) }],
312
- css: this.createCss(design, schema)
313
- // interactivity: {
314
- // layer: "layer0"
315
- // fields: ["id"]
316
- // }
317
- };
318
- return layerDef;
319
- }
320
- createMapnikJsonQL(design, schema, filters) {
321
- const axisBuilder = new AxisBuilder_1.default({ schema });
322
- const exprCompiler = new expressions_1.ExprCompiler(schema);
323
- /*
324
- Query:
325
- Works by first snapping to grid and then clustering the clusters with slower DBSCAN method
326
-
327
- select
328
- ST_Centroid(ST_Collect(center)) as the_geom_webmercator,
329
- sum(cnt) as cnt,
330
- log(sum(cnt)) * 6 + 14 as size from
331
- (
332
- select
333
- ST_ClusterDBSCAN(center, (!pixel_width!*30 + !pixel_height!*30)/2, 1) over () as clust,
334
- sub1.center as center,
335
- cnt as cnt
336
- from
337
- (
338
- select
339
- count(*) as cnt,
340
- ST_Centroid(ST_Collect(<geometry axis>)) as center,
341
- round(ST_XMin(<geometry axis>) / (!pixel_width!*40)) as gridx,
342
- round(ST_YMin(<geometry axis>) / (!pixel_width!*40)) as gridy,
343
- from <table> as main
344
- where <geometry axis> && !bbox!
345
- and <geometry axis> is not null
346
- and <other filters>
347
- group by 3, 4
348
- ) as sub1
349
- ) as sub2
350
- group by sub2.clust
351
-
352
- */
353
- // Compile geometry axis
354
- let geometryExpr = axisBuilder.compileAxis({ axis: design.axes.geometry, tableAlias: "main" });
355
- // ST_Centroid(ST_Collect(<geometry axis>))
356
- let centerExpr = {
357
- type: "op",
358
- op: "ST_Centroid",
359
- exprs: [
360
- {
361
- type: "op",
362
- op: "ST_Collect",
363
- exprs: [geometryExpr]
364
- }
365
- ]
366
- };
367
- const gridXExpr = {
368
- type: "op",
369
- op: "round",
370
- exprs: [
371
- {
372
- type: "op",
373
- op: "/",
374
- exprs: [
375
- { type: "op", op: "ST_XMin", exprs: [geometryExpr] },
376
- { type: "op", op: "*", exprs: [{ type: "token", token: "!pixel_width!" }, 40] }
377
- ]
378
- }
379
- ]
380
- };
381
- const gridYExpr = {
382
- type: "op",
383
- op: "round",
384
- exprs: [
385
- {
386
- type: "op",
387
- op: "/",
388
- exprs: [
389
- { type: "op", op: "ST_YMin", exprs: [geometryExpr] },
390
- { type: "op", op: "*", exprs: [{ type: "token", token: "!pixel_height!" }, 5] }
391
- ]
392
- }
393
- ]
394
- };
395
- // Create inner query
396
- const innerQuery = {
397
- type: "query",
398
- selects: [
399
- { type: "select", expr: { type: "op", op: "count", exprs: [] }, alias: "cnt" },
400
- { type: "select", expr: centerExpr, alias: "center" },
401
- { type: "select", expr: gridXExpr, alias: "gridx" },
402
- { type: "select", expr: gridYExpr, alias: "gridy" }
403
- ],
404
- from: exprCompiler.compileTable(design.table, "main"),
405
- groupBy: [3, 4]
406
- };
407
- // Create filters. First ensure geometry and limit to bounding box
408
- let whereClauses = [
409
- {
410
- type: "op",
411
- op: "&&",
412
- exprs: [geometryExpr, { type: "token", token: "!bbox!" }]
413
- }
414
- ];
415
- // Then add filters baked into layer
416
- if (design.filter) {
417
- whereClauses.push(exprCompiler.compileExpr({ expr: design.filter || null, tableAlias: "main" }));
418
- }
419
- // Then add extra filters passed in, if relevant
420
- // Get relevant filters
421
- const relevantFilters = lodash_1.default.where(filters, { table: design.table });
422
- for (let filter of relevantFilters) {
423
- whereClauses.push((0, expressions_1.injectTableAlias)(filter.jsonql, "main"));
424
- }
425
- whereClauses = lodash_1.default.compact(whereClauses);
426
- // Wrap if multiple
427
- if (whereClauses.length > 1) {
428
- innerQuery.where = { type: "op", op: "and", exprs: whereClauses };
429
- }
430
- else {
431
- innerQuery.where = whereClauses[0];
432
- }
433
- // Create next level
434
- // select
435
- // ST_ClusterDBSCAN(center, (!pixel_width!*30 + !pixel_height!*30)/2, 1) over () as clust,
436
- // sub1.center as center,
437
- // cnt as cnt from () as innerquery
438
- const clustExpr = {
439
- type: "op",
440
- op: "ST_ClusterDBSCAN",
441
- exprs: [
442
- { type: "field", tableAlias: "innerquery", column: "center" },
443
- {
444
- type: "op",
445
- op: "/",
446
- exprs: [
447
- {
448
- type: "op",
449
- op: "+",
450
- exprs: [
451
- { type: "op", op: "*", exprs: [{ type: "token", token: "!pixel_width!" }, 30] },
452
- { type: "op", op: "*", exprs: [{ type: "token", token: "!pixel_height!" }, 30] }
453
- ]
454
- },
455
- 2
456
- ]
457
- },
458
- 1
459
- ],
460
- over: {}
461
- };
462
- const inner2Query = {
463
- type: "query",
464
- selects: [
465
- { type: "select", expr: clustExpr, alias: "clust" },
466
- { type: "select", expr: { type: "field", tableAlias: "innerquery", column: "center" }, alias: "center" },
467
- { type: "select", expr: { type: "field", tableAlias: "innerquery", column: "cnt" }, alias: "cnt" }
468
- ],
469
- from: { type: "subquery", query: innerQuery, alias: "innerquery" }
470
- };
471
- // Create final level
472
- // ST_Centroid(ST_Collect(center)) as the_geom_webmercator,
473
- // sum(cnt) as cnt,
474
- // log(sum(cnt)) * 6 + 14 as size from
475
- // ST_Centroid(ST_Collect(center))
476
- centerExpr = {
477
- type: "op",
478
- op: "ST_Centroid",
479
- exprs: [
480
- {
481
- type: "op",
482
- op: "ST_Collect",
483
- exprs: [{ type: "field", tableAlias: "inner2query", column: "center" }]
484
- }
485
- ]
486
- };
487
- const cntExpr = {
488
- type: "op",
489
- op: "sum",
490
- exprs: [{ type: "field", tableAlias: "inner2query", column: "cnt" }]
491
- };
492
- const sizeExpr = {
493
- type: "op",
494
- op: "+",
495
- exprs: [{ type: "op", op: "*", exprs: [{ type: "op", op: "log", exprs: [cntExpr] }, 6] }, 14]
496
- };
497
- const query = {
498
- type: "query",
499
- selects: [
500
- { type: "select", expr: centerExpr, alias: "the_geom_webmercator" },
501
- { type: "select", expr: cntExpr, alias: "cnt" },
502
- { type: "select", expr: sizeExpr, alias: "size" }
503
- ],
504
- from: { type: "subquery", query: inner2Query, alias: "inner2query" },
505
- groupBy: [{ type: "field", tableAlias: "inner2query", column: "clust" }]
506
- };
507
- return query;
508
- }
509
- createCss(design, schema) {
510
- const css = `\
511
- #layer0 [cnt>1] {
512
- marker-width: [size];
513
- marker-line-color: white;
514
- marker-line-width: 4;
515
- marker-line-opacity: 0.6;
516
- marker-placement: point;
517
- marker-type: ellipse;
518
- marker-allow-overlap: true;
519
- marker-fill: ` +
520
- (design.fillColor || "#337ab7") +
521
- `;
522
- }
523
-
524
- #layer0::l1 [cnt>1] {
525
- text-name: [cnt];
526
- text-face-name: 'Arial Bold';
527
- text-allow-overlap: true;
528
- text-fill: ` +
529
- (design.textColor || "white") +
530
- `;
531
- }
532
-
533
- #layer0 [cnt=1] {
534
- marker-width: 10;
535
- marker-line-color: white;
536
- marker-line-width: 2;
537
- marker-line-opacity: 0.6;
538
- marker-placement: point;
539
- marker-type: ellipse;
540
- marker-allow-overlap: true;
541
- marker-fill: ` +
542
- (design.fillColor || "#337ab7") +
543
- `;
544
- }\
545
- `;
546
- return css;
547
- }
548
298
  // # Called when the interactivity grid is clicked.
549
299
  // # arguments:
550
300
  // # ev: { data: interactivty data e.g. `{ id: 123 }` }