@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.
- package/lib/ColorComponent.js +2 -2
- package/lib/TranslationsTabComponent.d.ts +34 -0
- package/lib/TranslationsTabComponent.js +256 -0
- package/lib/dashboards/DashboardComponent.js +1 -1
- package/lib/dashboards/ServerDashboardDataSource.d.ts +0 -1
- package/lib/dashboards/ServerDashboardDataSource.js +0 -15
- package/lib/dashboards/SettingsModalComponent.js +9 -233
- package/lib/datagrids/DatagridComponent.js +5 -0
- package/lib/datagrids/DatagridViewComponent.js +30 -4
- package/lib/maps/BufferLayer.d.ts +0 -13
- package/lib/maps/BufferLayer.js +12 -237
- package/lib/maps/BufferLayerDesignerComponent.d.ts +1 -1
- package/lib/maps/BufferLayerDesignerComponent.js +0 -5
- package/lib/maps/ChoroplethLayer.d.ts +1 -16
- package/lib/maps/ChoroplethLayer.js +13 -358
- package/lib/maps/ClusterLayer.d.ts +0 -9
- package/lib/maps/ClusterLayer.js +0 -250
- package/lib/maps/DirectMapDataSource.js +1 -38
- package/lib/maps/GridLayer.d.ts +0 -15
- package/lib/maps/GridLayer.js +0 -212
- package/lib/maps/Layer.d.ts +1 -26
- package/lib/maps/Layer.js +0 -13
- package/lib/maps/MapComponent.d.ts +19 -35
- package/lib/maps/MapComponent.js +135 -76
- package/lib/maps/MapControlComponent.d.ts +4 -5
- package/lib/maps/MapControlComponent.js +5 -12
- package/lib/maps/MapDesign.d.ts +8 -0
- package/lib/maps/MapDesignerComponent.d.ts +2 -0
- package/lib/maps/MapDesignerComponent.js +7 -2
- package/lib/maps/MapLayerDataSource.d.ts +0 -4
- package/lib/maps/MapLayerViewDesignerComponent.d.ts +3 -1
- package/lib/maps/MapLayerViewDesignerComponent.js +5 -1
- package/lib/maps/MapLayersDesignerComponent.d.ts +2 -0
- package/lib/maps/MapLayersDesignerComponent.js +2 -1
- package/lib/maps/MapTranslationsTab.d.ts +15 -0
- package/lib/maps/MapTranslationsTab.js +47 -0
- package/lib/maps/MapUtils.d.ts +11 -0
- package/lib/maps/MapUtils.js +47 -0
- package/lib/maps/MapViewComponent.d.ts +1 -1
- package/lib/maps/MapViewComponent.js +1 -8
- package/lib/maps/MarkersLayer.d.ts +1 -14
- package/lib/maps/MarkersLayer.js +71 -252
- package/lib/maps/MarkersLayerDesign.d.ts +4 -0
- package/lib/maps/MarkersLayerDesignerComponent.d.ts +20 -16
- package/lib/maps/MarkersLayerDesignerComponent.js +77 -23
- package/lib/maps/ServerMapDataSource.d.ts +0 -1
- package/lib/maps/ServerMapDataSource.js +0 -15
- package/lib/maps/SwitchableTileUrlLayer.d.ts +0 -2
- package/lib/maps/SwitchableTileUrlLayer.js +0 -9
- package/lib/maps/TileUrlLayer.d.ts +0 -1
- package/lib/maps/TileUrlLayer.js +0 -5
- package/lib/maps/VectorMapViewComponent.js +12 -1
- package/lib/maps/vectorMaps.d.ts +5 -6
- package/lib/maps/vectorMaps.js +13 -9
- package/lib/widgets/MapWidget.js +2 -1
- package/package.json +2 -2
- package/src/ColorComponent.tsx +2 -2
- package/src/TranslationsTabComponent.tsx +429 -0
- package/src/dashboards/DashboardComponent.tsx +1 -1
- package/src/dashboards/ServerDashboardDataSource.ts +0 -19
- package/src/dashboards/SettingsModalComponent.tsx +27 -383
- package/src/datagrids/DatagridComponent.tsx +6 -0
- package/src/datagrids/DatagridViewComponent.tsx +41 -5
- package/src/maps/BufferLayer.ts +16 -262
- package/src/maps/BufferLayerDesignerComponent.tsx +0 -6
- package/src/maps/ChoroplethLayer.ts +16 -393
- package/src/maps/ClusterLayer.ts +0 -274
- package/src/maps/DirectMapDataSource.ts +2 -49
- package/src/maps/GridLayer.ts +0 -224
- package/src/maps/Layer.ts +1 -35
- package/src/maps/MapComponent.tsx +448 -0
- package/src/maps/MapControlComponent.tsx +41 -0
- package/src/maps/MapDesign.ts +6 -0
- package/src/maps/MapDesignerComponent.tsx +18 -1
- package/src/maps/MapLayerDataSource.ts +0 -5
- package/src/maps/MapLayerViewDesignerComponent.ts +9 -2
- package/src/maps/MapLayersDesignerComponent.ts +4 -1
- package/src/maps/MapTranslationsTab.tsx +53 -0
- package/src/maps/MapUtils.ts +48 -0
- package/src/maps/MapViewComponent.tsx +2 -8
- package/src/maps/MarkersLayer.ts +79 -270
- package/src/maps/MarkersLayerDesign.ts +6 -0
- package/src/maps/MarkersLayerDesignerComponent.tsx +114 -38
- package/src/maps/ServerMapDataSource.ts +0 -19
- package/src/maps/SwitchableTileUrlLayer.tsx +0 -11
- package/src/maps/TileUrlLayer.tsx +0 -6
- package/src/maps/VectorMapViewComponent.tsx +13 -2
- package/src/maps/vectorMaps.tsx +12 -9
- package/src/widgets/MapWidget.tsx +2 -0
- package/src/maps/MapComponent.ts +0 -311
- package/src/maps/MapControlComponent.ts +0 -46
- 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:
|
|
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;
|
package/lib/maps/ClusterLayer.js
CHANGED
|
@@ -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 }` }
|