sea-chart 1.1.4 → 1.1.5
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/dist/assets/icons/funnel.svg +8 -0
- package/dist/components/font-settings/index.js +1 -2
- package/dist/constants/index.js +12 -1
- package/dist/constants/type-image.js +2 -1
- package/dist/constants/type.js +8 -2
- package/dist/locale/lang/de.js +13 -3
- package/dist/locale/lang/en.js +11 -1
- package/dist/locale/lang/es.js +11 -1
- package/dist/locale/lang/fr.js +13 -3
- package/dist/locale/lang/pt.js +11 -1
- package/dist/locale/lang/ru.js +11 -1
- package/dist/locale/lang/zh_CN.js +11 -1
- package/dist/model/funnel.js +50 -0
- package/dist/model/generic-model.js +1 -1
- package/dist/model/index.js +3 -1
- package/dist/settings/data-settings.js +5 -0
- package/dist/settings/funnel-settings/components/dnd-item/dnd-item.js +35 -0
- package/dist/settings/funnel-settings/components/dnd-item/dnd-item.module.css +22 -0
- package/dist/settings/funnel-settings/components/dnd-list.js +60 -0
- package/dist/settings/funnel-settings/components/funnel-label-setting.js +66 -0
- package/dist/settings/funnel-settings/components/funnel-layer-setting.js +72 -0
- package/dist/settings/funnel-settings/data-settings.js +69 -0
- package/dist/settings/funnel-settings/index.js +3 -0
- package/dist/settings/funnel-settings/style-settings.js +39 -0
- package/dist/settings/style-settings.js +5 -0
- package/dist/utils/chart-utils/base-utils.js +4 -0
- package/dist/utils/chart-utils/original-data-utils/basic-chart-calculator.js +1 -0
- package/dist/utils/chart-utils/original-data-utils/index.js +2 -1
- package/dist/utils/chart-utils/sql-statistics-utils.js +1 -0
- package/dist/utils/index.js +23 -0
- package/dist/utils/sql/chart-data-sql.js +1 -1
- package/dist/utils/sql/column-2-sql-column.js +2 -0
- package/dist/view/wrapper/bar.js +1 -1
- package/dist/view/wrapper/funnel.js +199 -0
- package/dist/view/wrapper/index.js +7 -0
- package/package.json +5 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
|
2
|
+
<!-- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -->
|
|
3
|
+
<svg viewBox="0 0 1024 1024" version="1.1" style="enable-background:new 0 0 1024 1024;" xmlns="http://www.w3.org/2000/svg">
|
|
4
|
+
<style type="text/css">
|
|
5
|
+
.st0{fill:#999999;}
|
|
6
|
+
</style>
|
|
7
|
+
<path class="st0" d="M1024.5 96c0 17.673-14.327 32-32 32H32.5c-17.673 0-32-14.327-32-32 0-17.673 14.327-32 32-32h960c17.673 0 32 14.327 32 32z" />
|
|
8
|
+
<path class="st0" d="M0.5 95.958h32v32H0.5zM421.5 95.958h32v32h-32zM992.5 95.958h32v32h-32zM640 959l-256-127V576h256zM926.398 128L816.685 256s-40.846-25.455-94.852-12.802c-99.37 23.282-83.758 80.729-168.495 76.802C476.954 316.459 448.5 246.979 325.6 230.604c-74.018-9.861-117.286 25.393-117.286 25.393L98.602 128H0l384 448h256l384-448h-97.602z" /></svg>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import intl from '../../intl';
|
|
2
|
+
import { Input } from 'reactstrap';
|
|
4
3
|
import { FontColorSettings, FontSizeSettings, FontWeightSettings } from '../../settings/widgets/font-settings';
|
|
5
4
|
import TextHorizontalSettings from '../../settings/widgets/text-horizontal-settings';
|
|
6
5
|
import { CHART_SUPPORT_FONT_WEIGHTS } from '../../constants';
|
package/dist/constants/index.js
CHANGED
|
@@ -6,7 +6,17 @@ import { CHART_TYPE, CHART_TYPE_SHOW, CHART_TYPES } from './type';
|
|
|
6
6
|
import { CHART_TYPE_IMAGE } from './type-image';
|
|
7
7
|
import { regions } from './regions';
|
|
8
8
|
import { TABLE_DIMENSIONS } from './table';
|
|
9
|
+
export const SETTING_DEFAULT_FONT_SIZE = 12;
|
|
9
10
|
export const DEFAULT_LANG = 'en';
|
|
11
|
+
export const FUNNEL_LABEL_FORMAT = {
|
|
12
|
+
NUMBER: 'number',
|
|
13
|
+
PERCENTAGE: 'percentage',
|
|
14
|
+
NUMBER_AND_PERCENTAGE: 'number_and_percentage'
|
|
15
|
+
};
|
|
16
|
+
export const FUNNEL_LABEL_POSITIONS = {
|
|
17
|
+
INSIDE: 'inside',
|
|
18
|
+
OUTSIDE: 'outside'
|
|
19
|
+
};
|
|
10
20
|
export const TREND_TYPES = {
|
|
11
21
|
UP: 'up',
|
|
12
22
|
DOWN: 'down',
|
|
@@ -311,7 +321,8 @@ export const xAxisMap = {
|
|
|
311
321
|
[CHART_TYPE.SCATTER]: 'x_axis_column_key',
|
|
312
322
|
[CHART_TYPE.TABLE]: 'groupby_column_key',
|
|
313
323
|
[CHART_TYPE.TREE_MAP]: 'groupby_column_key',
|
|
314
|
-
[CHART_TYPE.TREND]: 'date_column_key'
|
|
324
|
+
[CHART_TYPE.TREND]: 'date_column_key',
|
|
325
|
+
[CHART_TYPE.FUNNEL]: 'x_axis_column_key'
|
|
315
326
|
};
|
|
316
327
|
export const groupAxisMap = {
|
|
317
328
|
[CHART_TYPE.BAR_GROUP]: 'column_groupby_column_key',
|
|
@@ -28,5 +28,6 @@ export const CHART_TYPE_IMAGE = {
|
|
|
28
28
|
[CHART_TYPE.TREND]: 'trend-chart.png',
|
|
29
29
|
[CHART_TYPE.DASHBOARD]: 'dashboard-chart.png',
|
|
30
30
|
[CHART_TYPE.TREE_MAP]: 'treemap.png',
|
|
31
|
-
[CHART_TYPE.TABLE]: 'pivot-table.png'
|
|
31
|
+
[CHART_TYPE.TABLE]: 'pivot-table.png',
|
|
32
|
+
[CHART_TYPE.FUNNEL]: 'funnel.png'
|
|
32
33
|
};
|
package/dist/constants/type.js
CHANGED
|
@@ -27,7 +27,8 @@ export const CHART_TYPE = {
|
|
|
27
27
|
TREND: 'trend',
|
|
28
28
|
DASHBOARD: 'dashboard',
|
|
29
29
|
TREE_MAP: 'tree_map',
|
|
30
|
-
TABLE: 'table'
|
|
30
|
+
TABLE: 'table',
|
|
31
|
+
FUNNEL: 'funnel'
|
|
31
32
|
};
|
|
32
33
|
export const CHART_TYPE_SHOW = {
|
|
33
34
|
[CHART_TYPE.BAR]: 'Basic_histogram',
|
|
@@ -58,7 +59,8 @@ export const CHART_TYPE_SHOW = {
|
|
|
58
59
|
[CHART_TYPE.TREND]: 'Trend',
|
|
59
60
|
[CHART_TYPE.DASHBOARD]: 'Gauge',
|
|
60
61
|
[CHART_TYPE.TREE_MAP]: 'Tree_map',
|
|
61
|
-
[CHART_TYPE.TABLE]: 'Pivot_table'
|
|
62
|
+
[CHART_TYPE.TABLE]: 'Pivot_table',
|
|
63
|
+
[CHART_TYPE.FUNNEL]: 'Funnel'
|
|
62
64
|
};
|
|
63
65
|
export const CHART_TYPES = [{
|
|
64
66
|
name: 'Histogram',
|
|
@@ -116,4 +118,8 @@ export const CHART_TYPES = [{
|
|
|
116
118
|
name: 'Table',
|
|
117
119
|
icon: 'dtable-logo',
|
|
118
120
|
children: [CHART_TYPE.TABLE]
|
|
121
|
+
}, {
|
|
122
|
+
name: 'Funnel',
|
|
123
|
+
icon: 'funnel',
|
|
124
|
+
children: [CHART_TYPE.FUNNEL]
|
|
119
125
|
}];
|
package/dist/locale/lang/de.js
CHANGED
|
@@ -144,7 +144,7 @@ const de = {
|
|
|
144
144
|
"Internal_server_error": "Interner Serverfehler",
|
|
145
145
|
"Network_error": "Netzwerkfehler",
|
|
146
146
|
"Permission_denied": "Zugriff verweigert",
|
|
147
|
-
"Execution_time_of_the_query_exceeds_the_limit": "
|
|
147
|
+
"Execution_time_of_the_query_exceeds_the_limit": "Le temps d'exécution de la requête dépasse la limite. Les données ne peuvent pas être chargées.",
|
|
148
148
|
"There_are_some_problems_with_the_filters": "Die Filterbedingungen sind abnormal. Bitte setzen sie die Filterbedingungen in den Ansichtseinstellungen zurück.",
|
|
149
149
|
"Please_complete_the_chart_configuration_first": "Schließen Sie die Konfiguration des Diagramms ab.",
|
|
150
150
|
"Not_used": "Nicht verwendet",
|
|
@@ -253,7 +253,17 @@ const de = {
|
|
|
253
253
|
"Use_colors_in_single_select_solumn": "Verwenden Sie Farben in einer einzelnen Auswahlspalte",
|
|
254
254
|
"Search_records": "Einträge suchen",
|
|
255
255
|
"Please_select_a_grouping_column": "Please select grouping column",
|
|
256
|
-
"View": "
|
|
257
|
-
"
|
|
256
|
+
"View": "Sicht",
|
|
257
|
+
"Funnel": "Trichterdiagramm",
|
|
258
|
+
"All_charts": "Alle Diagramme",
|
|
259
|
+
"Show_legend": "Legende anzeigen",
|
|
260
|
+
"Funnel_layer": "Trichterschicht",
|
|
261
|
+
"funnel_accumulate_values": "Werte anhäufen",
|
|
262
|
+
"Show_funnel_layer_label": "Beschriftung der Trichterebene anzeigen",
|
|
263
|
+
"Inside": "Innen",
|
|
264
|
+
"Outside": "Draußen",
|
|
265
|
+
"Show_overall_rate": "Gesamtpreisbeschriftung anzeigen",
|
|
266
|
+
"Percentage": "Prozentsatz",
|
|
267
|
+
"Number_and_percentage": "Anzahl und Prozentsatz"
|
|
258
268
|
};
|
|
259
269
|
export default de;
|
package/dist/locale/lang/en.js
CHANGED
|
@@ -254,6 +254,16 @@ const en = {
|
|
|
254
254
|
"Search_records": "Search records",
|
|
255
255
|
"Please_select_a_grouping_column": "Please select grouping column",
|
|
256
256
|
"View": "View",
|
|
257
|
-
"
|
|
257
|
+
"Funnel": "Funnel",
|
|
258
|
+
"All_charts": "All charts",
|
|
259
|
+
"Show_legend": "Show legend",
|
|
260
|
+
"Funnel_layer": "Funnel layer",
|
|
261
|
+
"funnel_accumulate_values": "Funnel accumulate values",
|
|
262
|
+
"Show_funnel_layer_label": "Show funnel layer label",
|
|
263
|
+
"Inside": "Inside",
|
|
264
|
+
"Outside": "Outside",
|
|
265
|
+
"Show_overall_rate": "Show overall rate",
|
|
266
|
+
"Percentage": "Percentage",
|
|
267
|
+
"Number_and_percentage": "Number and percentage"
|
|
258
268
|
};
|
|
259
269
|
export default en;
|
package/dist/locale/lang/es.js
CHANGED
|
@@ -254,6 +254,16 @@ const es = {
|
|
|
254
254
|
"Search_records": "Search records",
|
|
255
255
|
"Please_select_a_grouping_column": "Please select grouping column",
|
|
256
256
|
"View": "View",
|
|
257
|
-
"
|
|
257
|
+
"Funnel": "Funnel",
|
|
258
|
+
"All_charts": "All charts",
|
|
259
|
+
"Show_legend": "Show legend",
|
|
260
|
+
"Funnel_layer": "Funnel layer",
|
|
261
|
+
"funnel_accumulate_values": "Funnel accumulate values",
|
|
262
|
+
"Show_funnel_layer_label": "Show funnel layer label",
|
|
263
|
+
"Inside": "Inside",
|
|
264
|
+
"Outside": "Outside",
|
|
265
|
+
"Show_overall_rate": "Show overall rate",
|
|
266
|
+
"Percentage": "Percentage",
|
|
267
|
+
"Number_and_percentage": "Number and percentage"
|
|
258
268
|
};
|
|
259
269
|
export default es;
|
package/dist/locale/lang/fr.js
CHANGED
|
@@ -144,7 +144,7 @@ const fr = {
|
|
|
144
144
|
"Internal_server_error": "Erreur interne du serveur",
|
|
145
145
|
"Network_error": "Erreur réseau",
|
|
146
146
|
"Permission_denied": "Permission refusée",
|
|
147
|
-
"Execution_time_of_the_query_exceeds_the_limit": "
|
|
147
|
+
"Execution_time_of_the_query_exceeds_the_limit": "Le temps d'exécution de la requête dépasse la limite. Les données ne peuvent pas être chargées.",
|
|
148
148
|
"There_are_some_problems_with_the_filters": "Les conditions de filtrage sont anormales. Veuillez réinitialiser les conditions de filtrage dans les paramètres de vue.",
|
|
149
149
|
"Please_complete_the_chart_configuration_first": "Compléter la configuration du diagramme",
|
|
150
150
|
"Not_used": "Ne pas utilisé",
|
|
@@ -253,7 +253,17 @@ const fr = {
|
|
|
253
253
|
"Use_colors_in_single_select_solumn": "Utiliser les couleurs dans une colonne de sélection unique",
|
|
254
254
|
"Search_records": "Rechercher des enregistrements",
|
|
255
255
|
"Please_select_a_grouping_column": "Please select grouping column",
|
|
256
|
-
"View": "
|
|
257
|
-
"
|
|
256
|
+
"View": "Voir",
|
|
257
|
+
"Funnel": "Entonnoir",
|
|
258
|
+
"All_charts": "Tous les graphiques",
|
|
259
|
+
"Show_legend": "Afficher la légende",
|
|
260
|
+
"Funnel_layer": "Couche d'entonnoir",
|
|
261
|
+
"funnel_accumulate_values": "accumuler des valeurs",
|
|
262
|
+
"Show_funnel_layer_label": "Afficher l'étiquette du calque",
|
|
263
|
+
"Inside": "À l'intérieur",
|
|
264
|
+
"Outside": "Dehors",
|
|
265
|
+
"Show_overall_rate": "Afficher le libellé du tarif global",
|
|
266
|
+
"Percentage": "Couche d'entonnoir",
|
|
267
|
+
"Number_and_percentage": "Nombre et pourcentage"
|
|
258
268
|
};
|
|
259
269
|
export default fr;
|
package/dist/locale/lang/pt.js
CHANGED
|
@@ -254,6 +254,16 @@ const pt = {
|
|
|
254
254
|
"Search_records": "Search records",
|
|
255
255
|
"Please_select_a_grouping_column": "Please select grouping column",
|
|
256
256
|
"View": "View",
|
|
257
|
-
"
|
|
257
|
+
"Funnel": "Funnel",
|
|
258
|
+
"All_charts": "All charts",
|
|
259
|
+
"Show_legend": "Show legend",
|
|
260
|
+
"Funnel_layer": "Funnel layer",
|
|
261
|
+
"funnel_accumulate_values": "Funnel accumulate values",
|
|
262
|
+
"Show_funnel_layer_label": "Show funnel layer label",
|
|
263
|
+
"Inside": "Inside",
|
|
264
|
+
"Outside": "Outside",
|
|
265
|
+
"Show_overall_rate": "Show overall rate",
|
|
266
|
+
"Percentage": "Percentage",
|
|
267
|
+
"Number_and_percentage": "Number and percentage"
|
|
258
268
|
};
|
|
259
269
|
export default pt;
|
package/dist/locale/lang/ru.js
CHANGED
|
@@ -254,6 +254,16 @@ const ru = {
|
|
|
254
254
|
"Search_records": "Search records",
|
|
255
255
|
"Please_select_a_grouping_column": "Please select grouping column",
|
|
256
256
|
"View": "View",
|
|
257
|
-
"
|
|
257
|
+
"Funnel": "Funnel",
|
|
258
|
+
"All_charts": "All charts",
|
|
259
|
+
"Show_legend": "Show legend",
|
|
260
|
+
"Funnel_layer": "Funnel layer",
|
|
261
|
+
"funnel_accumulate_values": "Funnel accumulate values",
|
|
262
|
+
"Show_funnel_layer_label": "Show funnel layer label",
|
|
263
|
+
"Inside": "Inside",
|
|
264
|
+
"Outside": "Outside",
|
|
265
|
+
"Show_overall_rate": "Show overall rate",
|
|
266
|
+
"Percentage": "Percentage",
|
|
267
|
+
"Number_and_percentage": "Number and percentage"
|
|
258
268
|
};
|
|
259
269
|
export default ru;
|
|
@@ -254,6 +254,16 @@ const zh_CN = {
|
|
|
254
254
|
"Search_records": "搜索记录",
|
|
255
255
|
"Please_select_a_grouping_column": "请选择一个分组列",
|
|
256
256
|
"View": "视图",
|
|
257
|
-
"
|
|
257
|
+
"Funnel": "漏斗图",
|
|
258
|
+
"All_charts": "所有图表",
|
|
259
|
+
"Show_legend": "显示图例",
|
|
260
|
+
"Funnel_layer": "漏斗层",
|
|
261
|
+
"funnel_accumulate_values": "每层累计当前层与所有下层数值",
|
|
262
|
+
"Show_funnel_layer_label": "显示漏斗层标签",
|
|
263
|
+
"Inside": "在图表内",
|
|
264
|
+
"Outside": "在图表外",
|
|
265
|
+
"Show_overall_rate": "显示总转化率标签",
|
|
266
|
+
"Percentage": "百分比",
|
|
267
|
+
"Number_and_percentage": "数值和百分比"
|
|
258
268
|
};
|
|
259
269
|
export default zh_CN;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CellType } from 'dtable-utils';
|
|
2
|
+
import { cloneDeep } from 'lodash-es';
|
|
3
|
+
import { CHART_SUMMARY_TYPE, CHART_TYPE, FUNNEL_LABEL_FORMAT, FUNNEL_LABEL_POSITIONS } from '../constants';
|
|
4
|
+
import BaseModel from './base-model';
|
|
5
|
+
class Funnel extends BaseModel {
|
|
6
|
+
constructor(options, tables) {
|
|
7
|
+
var _options$x_axis_optio;
|
|
8
|
+
options = options._options;
|
|
9
|
+
super({
|
|
10
|
+
...options,
|
|
11
|
+
type: CHART_TYPE.FUNNEL
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// init a default value here so settings won't need to check existence of the config
|
|
15
|
+
|
|
16
|
+
// data-settings
|
|
17
|
+
// needs to be single select
|
|
18
|
+
this.x_axis_column_key = options.x_axis_column_key;
|
|
19
|
+
|
|
20
|
+
// if no x_axis_option_list but x_axis_column_key is single select type, then use it's option list
|
|
21
|
+
if ((_options$x_axis_optio = options.x_axis_option_list) === null || _options$x_axis_optio === void 0 ? void 0 : _options$x_axis_optio.length) {
|
|
22
|
+
this.x_axis_option_list = options.x_axis_option_list;
|
|
23
|
+
} else if (options.table_id && options.x_axis_column_key) {
|
|
24
|
+
const table = tables.find(table => table._id === options.table_id);
|
|
25
|
+
const column = table.columns.find(column => column.key === options.x_axis_column_key);
|
|
26
|
+
if (column.type === CellType.SINGLE_SELECT) {
|
|
27
|
+
this.x_axis_option_list = cloneDeep(column.data.options);
|
|
28
|
+
} else {
|
|
29
|
+
this.x_axis_option_list = [];
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
this.x_axis_option_list = [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// y-axis
|
|
36
|
+
this.y_axis_summary_column_key = options.y_axis_summary_column_key;
|
|
37
|
+
this.y_axis_summary_type = options.y_axis_summary_type || CHART_SUMMARY_TYPE.COUNT;
|
|
38
|
+
this.y_axis_summary_method = options.y_axis_summary_method || CHART_SUMMARY_TYPE.SUM;
|
|
39
|
+
this.funnel_accumulate_values = typeof options.funnel_accumulate_values !== 'boolean' ? true : options.funnel_accumulate_values;
|
|
40
|
+
|
|
41
|
+
// style settings
|
|
42
|
+
this.funnel_show_legend = typeof options.funnel_show_legend !== 'boolean' ? true : options.funnel_show_legend;
|
|
43
|
+
this.funnel_show_labels = typeof options.funnel_show_labels !== 'boolean' ? true : options.funnel_show_labels;
|
|
44
|
+
this.funnel_label_position = options.funnel_label_position || FUNNEL_LABEL_POSITIONS.OUTSIDE;
|
|
45
|
+
this.funnel_label_format = options.funnel_label_format || FUNNEL_LABEL_FORMAT.NUMBER;
|
|
46
|
+
this.funnel_label_font_size = options.funnel_label_font_size || 12;
|
|
47
|
+
this.funnel_show_overall_rate = typeof options.funnel_show_overall_rate !== 'boolean' ? true : options.funnel_show_overall_rate;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export default Funnel;
|
|
@@ -6,7 +6,7 @@ export default class GenericModel extends BaseModel {
|
|
|
6
6
|
constructor(object) {
|
|
7
7
|
const options = object || {};
|
|
8
8
|
super(options);
|
|
9
|
-
|
|
9
|
+
this._options = object;
|
|
10
10
|
// x data
|
|
11
11
|
this.x_axis_column_key = getChartConfigValueByKey(GENERIC_KEY.X_AXIS_COLUMN_KEY, options);
|
|
12
12
|
this.x_axis_include_empty_cells = getChartConfigValueByKey(GENERIC_KEY.X_AXIS_INCLUDE_EMPTY_CELLS, options);
|
package/dist/model/index.js
CHANGED
|
@@ -31,6 +31,7 @@ import BasicNumberCard from './basic-number-card';
|
|
|
31
31
|
import Trend from './trend';
|
|
32
32
|
import Dashboard from './dashboard';
|
|
33
33
|
import Table from './table';
|
|
34
|
+
import Funnel from './funnel';
|
|
34
35
|
const CHART_MAP = {
|
|
35
36
|
[CHART_TYPE.BAR]: Bar,
|
|
36
37
|
[CHART_TYPE.BAR_GROUP]: BarGroup,
|
|
@@ -60,6 +61,7 @@ const CHART_MAP = {
|
|
|
60
61
|
[CHART_TYPE.TREND]: Trend,
|
|
61
62
|
[CHART_TYPE.DASHBOARD]: Dashboard,
|
|
62
63
|
[CHART_TYPE.TREE_MAP]: TreeMap,
|
|
63
|
-
[CHART_TYPE.TABLE]: Table
|
|
64
|
+
[CHART_TYPE.TABLE]: Table,
|
|
65
|
+
[CHART_TYPE.FUNNEL]: Funnel
|
|
64
66
|
};
|
|
65
67
|
export { ChartModel, GenericModel, CHART_MAP, User };
|
|
@@ -15,6 +15,7 @@ import { MapDataSettings } from './map-settings';
|
|
|
15
15
|
import { HeatMapDataSettings } from './heat-map-settings';
|
|
16
16
|
import { MirrorDataSettings } from './mirror-settings';
|
|
17
17
|
import { TrendDataSettings } from './trend-settings';
|
|
18
|
+
import { FunnelDataSettings } from './funnel-settings';
|
|
18
19
|
const DataSettings = props => {
|
|
19
20
|
const [refreshToggle, setRefreshToggle] = useState(false);
|
|
20
21
|
const cacheRef = useRef(props);
|
|
@@ -122,6 +123,10 @@ const DataSettings = props => {
|
|
|
122
123
|
{
|
|
123
124
|
return /*#__PURE__*/React.createElement(TrendDataSettings, props);
|
|
124
125
|
}
|
|
126
|
+
case CHART_TYPE.FUNNEL:
|
|
127
|
+
{
|
|
128
|
+
return /*#__PURE__*/React.createElement(FunnelDataSettings, props);
|
|
129
|
+
}
|
|
125
130
|
default:
|
|
126
131
|
{
|
|
127
132
|
return null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useSortable } from '@dnd-kit/sortable';
|
|
2
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import styles from './dnd-item.module.css';
|
|
5
|
+
export default function DndItem(_ref) {
|
|
6
|
+
let {
|
|
7
|
+
option
|
|
8
|
+
} = _ref;
|
|
9
|
+
const {
|
|
10
|
+
setNodeRef,
|
|
11
|
+
attributes,
|
|
12
|
+
listeners,
|
|
13
|
+
transform,
|
|
14
|
+
transition
|
|
15
|
+
} = useSortable({
|
|
16
|
+
id: option.id,
|
|
17
|
+
transition: {
|
|
18
|
+
duration: 500,
|
|
19
|
+
easing: 'cubic-bezier(0.25, 1, 0.5, 1)'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const dndStyles = {
|
|
23
|
+
transform: CSS.Transform.toString(transform),
|
|
24
|
+
transition
|
|
25
|
+
};
|
|
26
|
+
return /*#__PURE__*/React.createElement("div", Object.assign({
|
|
27
|
+
className: styles['dnd-item'],
|
|
28
|
+
ref: setNodeRef,
|
|
29
|
+
style: dndStyles
|
|
30
|
+
}, attributes, listeners), /*#__PURE__*/React.createElement("i", {
|
|
31
|
+
className: "dtable-font dtable-icon-drag pr-2"
|
|
32
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
33
|
+
className: styles['option-name']
|
|
34
|
+
}, option.name));
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
.dnd-item {
|
|
3
|
+
width: 100%;
|
|
4
|
+
border-radius: 6px;
|
|
5
|
+
height: 30px;
|
|
6
|
+
background-color: #f2f3f5;
|
|
7
|
+
margin-bottom: 10px;
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.option-name {
|
|
13
|
+
width: calc(100% - 30px);
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
text-overflow: ellipsis;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:global(.dnd-list-container .dtable-font.dtable-icon-drag) {
|
|
20
|
+
font-size: 12px;
|
|
21
|
+
padding-left: 10px;
|
|
22
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { DndContext } from '@dnd-kit/core';
|
|
3
|
+
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
4
|
+
import { restrictToParentElement } from '@dnd-kit/modifiers';
|
|
5
|
+
import DndItem from './dnd-item/dnd-item';
|
|
6
|
+
const getIndex = (active, over, array) => {
|
|
7
|
+
let activeIndex = 0;
|
|
8
|
+
let overIndex = 0;
|
|
9
|
+
try {
|
|
10
|
+
array.forEach((item, index) => {
|
|
11
|
+
if (active.id === item.id) {
|
|
12
|
+
activeIndex = index;
|
|
13
|
+
}
|
|
14
|
+
if (over.id === item.id) {
|
|
15
|
+
overIndex = index;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
} catch (error) {
|
|
19
|
+
// reset
|
|
20
|
+
overIndex = activeIndex;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
activeIndex,
|
|
24
|
+
overIndex
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export default function DndList(_ref) {
|
|
28
|
+
let {
|
|
29
|
+
list,
|
|
30
|
+
onListChange
|
|
31
|
+
} = _ref;
|
|
32
|
+
const handleDragEnd = useCallback(_ref2 => {
|
|
33
|
+
let {
|
|
34
|
+
active,
|
|
35
|
+
over
|
|
36
|
+
} = _ref2;
|
|
37
|
+
if (over && active.id !== over.id) {
|
|
38
|
+
const {
|
|
39
|
+
activeIndex,
|
|
40
|
+
overIndex
|
|
41
|
+
} = getIndex(active, over, list);
|
|
42
|
+
const newList = [...list];
|
|
43
|
+
const newSortedList = arrayMove(newList, activeIndex, overIndex);
|
|
44
|
+
onListChange(newSortedList);
|
|
45
|
+
}
|
|
46
|
+
}, [list, onListChange]);
|
|
47
|
+
if (!(list === null || list === void 0 ? void 0 : list.length)) return null;
|
|
48
|
+
return /*#__PURE__*/React.createElement(DndContext, {
|
|
49
|
+
onDragEnd: handleDragEnd,
|
|
50
|
+
modifiers: [restrictToParentElement]
|
|
51
|
+
}, /*#__PURE__*/React.createElement(SortableContext, {
|
|
52
|
+
items: list.map(item => item.id),
|
|
53
|
+
strategy: verticalListSortingStrategy
|
|
54
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
55
|
+
className: "dnd-list-container"
|
|
56
|
+
}, list.map(item => /*#__PURE__*/React.createElement(DndItem, {
|
|
57
|
+
option: item,
|
|
58
|
+
key: item.id
|
|
59
|
+
})))));
|
|
60
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import _CollapsibleSettingLayout from "dtable-ui-component/lib/CollapsibleSettingLayout";
|
|
2
|
+
import _DTableSelect from "dtable-ui-component/lib/DTableSelect";
|
|
3
|
+
import _DTableSwitch from "dtable-ui-component/lib/DTableSwitch";
|
|
4
|
+
import React, { useCallback } from 'react';
|
|
5
|
+
import { FormGroup, Label } from 'reactstrap';
|
|
6
|
+
import intl from '../../../intl';
|
|
7
|
+
import { FontSizeSettings } from '../../widgets/font-settings';
|
|
8
|
+
import { FUNNEL_LABEL_FORMAT, FUNNEL_LABEL_POSITIONS, SETTING_DEFAULT_FONT_SIZE } from '../../../constants';
|
|
9
|
+
const labelPositionOptions = [{
|
|
10
|
+
value: FUNNEL_LABEL_POSITIONS.INSIDE,
|
|
11
|
+
label: /*#__PURE__*/React.createElement("span", null, intl.get('Inside'))
|
|
12
|
+
}, {
|
|
13
|
+
value: FUNNEL_LABEL_POSITIONS.OUTSIDE,
|
|
14
|
+
label: /*#__PURE__*/React.createElement("span", null, intl.get('Outside'))
|
|
15
|
+
}];
|
|
16
|
+
const labelFormatOptions = [{
|
|
17
|
+
value: FUNNEL_LABEL_FORMAT.NUMBER,
|
|
18
|
+
label: /*#__PURE__*/React.createElement("span", null, intl.get('Number'))
|
|
19
|
+
}, {
|
|
20
|
+
value: FUNNEL_LABEL_FORMAT.PERCENTAGE,
|
|
21
|
+
label: /*#__PURE__*/React.createElement("span", null, intl.get('Percentage'))
|
|
22
|
+
}, {
|
|
23
|
+
value: FUNNEL_LABEL_FORMAT.NUMBER_AND_PERCENTAGE,
|
|
24
|
+
label: /*#__PURE__*/React.createElement("span", null, intl.get('Number_and_percentage'))
|
|
25
|
+
}];
|
|
26
|
+
export default function FunnelLabelSetting(_ref) {
|
|
27
|
+
let {
|
|
28
|
+
onChange,
|
|
29
|
+
funnelLabelPosition,
|
|
30
|
+
funnelLabelFormat,
|
|
31
|
+
funnelLabelFontSize,
|
|
32
|
+
funnelShowOverallRate,
|
|
33
|
+
funnelShowLabels
|
|
34
|
+
} = _ref;
|
|
35
|
+
const handleChange = useCallback(function handleChange(value, key) {
|
|
36
|
+
onChange && onChange({
|
|
37
|
+
[key]: value
|
|
38
|
+
});
|
|
39
|
+
}, [onChange]);
|
|
40
|
+
const selectedLabelPosition = labelPositionOptions.find(option => option.value === funnelLabelPosition);
|
|
41
|
+
const selectedLabelFormat = labelFormatOptions.find(option => option.value === funnelLabelFormat);
|
|
42
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(_CollapsibleSettingLayout, {
|
|
43
|
+
title: intl.get('Label')
|
|
44
|
+
}, /*#__PURE__*/React.createElement(FormGroup, null, /*#__PURE__*/React.createElement(_DTableSwitch, {
|
|
45
|
+
checked: funnelShowLabels,
|
|
46
|
+
placeholder: intl.get('Show_funnel_layer_label'),
|
|
47
|
+
onChange: e => handleChange(e.target.checked, 'funnel_show_labels')
|
|
48
|
+
})), funnelShowLabels && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(FormGroup, null, /*#__PURE__*/React.createElement(Label, null, intl.get('Label_position')), /*#__PURE__*/React.createElement(_DTableSelect, {
|
|
49
|
+
value: selectedLabelPosition || labelPositionOptions[1],
|
|
50
|
+
onChange: e => handleChange(e.value, 'funnel_label_position'),
|
|
51
|
+
options: labelPositionOptions
|
|
52
|
+
})), /*#__PURE__*/React.createElement(FormGroup, null, /*#__PURE__*/React.createElement(Label, null, intl.get('Label_format')), /*#__PURE__*/React.createElement(_DTableSelect, {
|
|
53
|
+
value: selectedLabelFormat || labelFormatOptions[0],
|
|
54
|
+
onChange: e => handleChange(e.value, 'funnel_label_format'),
|
|
55
|
+
options: labelFormatOptions
|
|
56
|
+
})), /*#__PURE__*/React.createElement(FormGroup, null, /*#__PURE__*/React.createElement(FontSizeSettings, {
|
|
57
|
+
className: 'mt-3',
|
|
58
|
+
fontSize: funnelLabelFontSize,
|
|
59
|
+
defaultFontSize: SETTING_DEFAULT_FONT_SIZE,
|
|
60
|
+
modifyFontSize: e => handleChange(e, 'funnel_label_font_size')
|
|
61
|
+
})), /*#__PURE__*/React.createElement(FormGroup, null, /*#__PURE__*/React.createElement(_DTableSwitch, {
|
|
62
|
+
checked: funnelShowOverallRate,
|
|
63
|
+
placeholder: intl.get('Show_overall_rate'),
|
|
64
|
+
onChange: e => handleChange(e.target.checked, 'funnel_show_overall_rate')
|
|
65
|
+
})))));
|
|
66
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import _DTableSelect from "dtable-ui-component/lib/DTableSelect";
|
|
2
|
+
import { COLUMNS_ICON_CONFIG } from 'dtable-utils';
|
|
3
|
+
import React, { useCallback, useMemo } from 'react';
|
|
4
|
+
import { FormGroup, Label } from 'reactstrap';
|
|
5
|
+
import { cloneDeep } from 'lodash-es';
|
|
6
|
+
import intl from '../../../intl';
|
|
7
|
+
import DndList from './dnd-list';
|
|
8
|
+
export default function FunnelLayerSetting(_ref) {
|
|
9
|
+
let {
|
|
10
|
+
selectedColumnKey,
|
|
11
|
+
x_axis_option_list,
|
|
12
|
+
columnList,
|
|
13
|
+
onChange
|
|
14
|
+
} = _ref;
|
|
15
|
+
const options = useMemo(() => columnList.map(column => {
|
|
16
|
+
return {
|
|
17
|
+
value: column,
|
|
18
|
+
label: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
|
19
|
+
className: "header-icon"
|
|
20
|
+
}, /*#__PURE__*/React.createElement("i", {
|
|
21
|
+
className: COLUMNS_ICON_CONFIG[column.type]
|
|
22
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
23
|
+
className: 'select-option-name'
|
|
24
|
+
}, column.name))
|
|
25
|
+
};
|
|
26
|
+
}), [columnList]);
|
|
27
|
+
|
|
28
|
+
// selectedColumnKey maybe inherited from another chart and so maybe it's not a singleSelect column
|
|
29
|
+
// columnList here is a list of singleSelect columns
|
|
30
|
+
const selectedColumn = columnList.find(c => c.key === selectedColumnKey);
|
|
31
|
+
|
|
32
|
+
// if selectedColumn is a singleSelect column, then use it's option and option's list
|
|
33
|
+
let selectedOption,
|
|
34
|
+
singleSelectOptionsList = [];
|
|
35
|
+
if (selectedColumnKey && selectedColumn) {
|
|
36
|
+
selectedOption = {
|
|
37
|
+
value: selectedColumn,
|
|
38
|
+
label: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
|
39
|
+
className: "header-icon"
|
|
40
|
+
}, /*#__PURE__*/React.createElement("i", {
|
|
41
|
+
className: COLUMNS_ICON_CONFIG[selectedColumn.type]
|
|
42
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
43
|
+
className: 'select-option-name'
|
|
44
|
+
}, selectedColumn.name))
|
|
45
|
+
};
|
|
46
|
+
singleSelectOptionsList = x_axis_option_list;
|
|
47
|
+
}
|
|
48
|
+
const handleXAxisColumnKeyChange = useCallback(option => {
|
|
49
|
+
const {
|
|
50
|
+
value
|
|
51
|
+
} = option;
|
|
52
|
+
const newList = cloneDeep(value.data.options);
|
|
53
|
+
onChange({
|
|
54
|
+
'x_axis_column_key': value.key,
|
|
55
|
+
'x_axis_option_list': newList
|
|
56
|
+
});
|
|
57
|
+
}, [onChange]);
|
|
58
|
+
const handleListOrderChange = useCallback(newList => {
|
|
59
|
+
onChange({
|
|
60
|
+
'x_axis_option_list': newList
|
|
61
|
+
});
|
|
62
|
+
}, [onChange]);
|
|
63
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(FormGroup, null, /*#__PURE__*/React.createElement(Label, null, intl.get('Funnel_layer')), /*#__PURE__*/React.createElement(_DTableSelect, {
|
|
64
|
+
value: selectedOption,
|
|
65
|
+
options: options,
|
|
66
|
+
onChange: handleXAxisColumnKeyChange
|
|
67
|
+
})), /*#__PURE__*/React.createElement(DndList, {
|
|
68
|
+
selectedColumnKey: selectedColumnKey,
|
|
69
|
+
list: singleSelectOptionsList,
|
|
70
|
+
onListChange: handleListOrderChange
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import _DTableSwitch from "dtable-ui-component/lib/DTableSwitch";
|
|
2
|
+
import React, { useCallback } from 'react';
|
|
3
|
+
import { CellType } from 'dtable-utils';
|
|
4
|
+
import CommonDataSettings from '../widgets/common-data-settings';
|
|
5
|
+
import Divider from '../widgets/divider';
|
|
6
|
+
import BasicSummary from '../widgets/basic-summary';
|
|
7
|
+
import intl from '../../intl';
|
|
8
|
+
import { CHART_SUMMARY_TYPES, CHART_TYPE } from '../../constants';
|
|
9
|
+
import FunnelLayerSetting from './components/funnel-layer-setting';
|
|
10
|
+
export default function DataSetting(_ref) {
|
|
11
|
+
let {
|
|
12
|
+
dataSources,
|
|
13
|
+
chart,
|
|
14
|
+
tables,
|
|
15
|
+
onChange
|
|
16
|
+
} = _ref;
|
|
17
|
+
const {
|
|
18
|
+
x_axis_column_key,
|
|
19
|
+
table_id,
|
|
20
|
+
type,
|
|
21
|
+
x_axis_option_list,
|
|
22
|
+
funnel_accumulate_values
|
|
23
|
+
} = chart.config;
|
|
24
|
+
let singleSelectColumns;
|
|
25
|
+
if (table_id) {
|
|
26
|
+
const selectedTable = tables.find(table => table._id === table_id);
|
|
27
|
+
// only use single select columns
|
|
28
|
+
singleSelectColumns = selectedTable.columns.filter(column => column.type === CellType.SINGLE_SELECT);
|
|
29
|
+
} else {
|
|
30
|
+
singleSelectColumns = [];
|
|
31
|
+
}
|
|
32
|
+
const handleFunnelAccumulateValuesChange = useCallback(function (e) {
|
|
33
|
+
onChange && onChange({
|
|
34
|
+
funnel_accumulate_values: e.target.checked
|
|
35
|
+
});
|
|
36
|
+
}, [onChange]);
|
|
37
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(CommonDataSettings, {
|
|
38
|
+
dataSources: dataSources,
|
|
39
|
+
chart: chart,
|
|
40
|
+
tables: tables,
|
|
41
|
+
onChange: onChange
|
|
42
|
+
}), /*#__PURE__*/React.createElement(FunnelLayerSetting, {
|
|
43
|
+
selectedColumnKey: x_axis_column_key,
|
|
44
|
+
x_axis_option_list: x_axis_option_list,
|
|
45
|
+
columnList: singleSelectColumns,
|
|
46
|
+
onChange: onChange
|
|
47
|
+
}), /*#__PURE__*/React.createElement(Divider, {
|
|
48
|
+
className: "mt-4"
|
|
49
|
+
}), /*#__PURE__*/React.createElement(BasicSummary, {
|
|
50
|
+
className: "selected-y-axis",
|
|
51
|
+
label: intl.get('Y_axis'),
|
|
52
|
+
showSummaryTypes: type === CHART_TYPE.BAR_CUSTOM ? false : true,
|
|
53
|
+
summaryTypeKey: 'y_axis_summary_type',
|
|
54
|
+
summaryMethodKey: 'y_axis_summary_method',
|
|
55
|
+
summaryColumnKey: 'y_axis_summary_column_key',
|
|
56
|
+
chart: chart,
|
|
57
|
+
selectedTableId: table_id,
|
|
58
|
+
tables: tables,
|
|
59
|
+
supportColumnTypes: [CellType.NUMBER],
|
|
60
|
+
summaryTypeOptions: CHART_SUMMARY_TYPES,
|
|
61
|
+
onChange: onChange
|
|
62
|
+
}), /*#__PURE__*/React.createElement(Divider, {
|
|
63
|
+
className: "mt-4"
|
|
64
|
+
}), /*#__PURE__*/React.createElement(_DTableSwitch, {
|
|
65
|
+
checked: funnel_accumulate_values,
|
|
66
|
+
placeholder: intl.get('funnel_accumulate_values'),
|
|
67
|
+
onChange: handleFunnelAccumulateValuesChange
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import _DTableSwitch from "dtable-ui-component/lib/DTableSwitch";
|
|
2
|
+
import React, { useCallback } from 'react';
|
|
3
|
+
import intl from '../../intl';
|
|
4
|
+
import Divider from '../widgets/divider';
|
|
5
|
+
import FunnelLabelSetting from './components/funnel-label-setting';
|
|
6
|
+
export default function StyleSetting(_ref) {
|
|
7
|
+
let {
|
|
8
|
+
chart,
|
|
9
|
+
onChange
|
|
10
|
+
} = _ref;
|
|
11
|
+
const {
|
|
12
|
+
funnel_show_legend,
|
|
13
|
+
funnel_show_labels,
|
|
14
|
+
funnel_label_position,
|
|
15
|
+
funnel_label_format,
|
|
16
|
+
funnel_label_font_size,
|
|
17
|
+
funnel_show_overall_rate
|
|
18
|
+
} = chart.config;
|
|
19
|
+
const handleShowLegendChange = useCallback(function (event) {
|
|
20
|
+
const value = event.target.checked;
|
|
21
|
+
onChange && onChange({
|
|
22
|
+
funnel_show_legend: value
|
|
23
|
+
});
|
|
24
|
+
}, [onChange]);
|
|
25
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(_DTableSwitch, {
|
|
26
|
+
checked: funnel_show_legend,
|
|
27
|
+
placeholder: intl.get('Show_legend'),
|
|
28
|
+
onChange: handleShowLegendChange
|
|
29
|
+
}), /*#__PURE__*/React.createElement(Divider, {
|
|
30
|
+
className: "mt-4"
|
|
31
|
+
}), /*#__PURE__*/React.createElement(FunnelLabelSetting, {
|
|
32
|
+
onChange: onChange,
|
|
33
|
+
funnelShowLabels: funnel_show_labels,
|
|
34
|
+
funnelLabelPosition: funnel_label_position,
|
|
35
|
+
funnelLabelFormat: funnel_label_format,
|
|
36
|
+
funnelLabelFontSize: funnel_label_font_size,
|
|
37
|
+
funnelShowOverallRate: funnel_show_overall_rate
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
@@ -13,6 +13,7 @@ import StatisticTitleSetting from './widgets/title-settings';
|
|
|
13
13
|
import { CompletenessStyleSettings } from './completeness-settings';
|
|
14
14
|
import { MapStyleSettings } from './map-settings';
|
|
15
15
|
import { TrendStyleSettings } from './trend-settings';
|
|
16
|
+
import { FunnelStyleSettings } from './funnel-settings';
|
|
16
17
|
const StyleSettings = _ref => {
|
|
17
18
|
let {
|
|
18
19
|
chart,
|
|
@@ -131,6 +132,10 @@ const StyleSettings = _ref => {
|
|
|
131
132
|
{
|
|
132
133
|
return /*#__PURE__*/React.createElement(TrendStyleSettings, props);
|
|
133
134
|
}
|
|
135
|
+
case CHART_TYPE.FUNNEL:
|
|
136
|
+
{
|
|
137
|
+
return /*#__PURE__*/React.createElement(FunnelStyleSettings, props);
|
|
138
|
+
}
|
|
134
139
|
default:
|
|
135
140
|
{
|
|
136
141
|
return null;
|
|
@@ -89,6 +89,10 @@ BaseUtils.isValidExistChart = (tables, chart) => {
|
|
|
89
89
|
}
|
|
90
90
|
if (!groupByColumnKey) return false;
|
|
91
91
|
if (!getTableColumnByKey(table, groupByColumnKey)) return false;
|
|
92
|
+
if (type === CHART_TYPE.FUNNEL) {
|
|
93
|
+
const column = getColumnByKey(config.x_axis_column_key, table.columns);
|
|
94
|
+
if (column.type !== CellType.SINGLE_SELECT) return false;
|
|
95
|
+
}
|
|
92
96
|
if (type === CHART_TYPE.COMBINATION) {
|
|
93
97
|
const isExist = _BaseUtils.isValidCombinationChart(config, table);
|
|
94
98
|
if (!isExist) return false;
|
|
@@ -87,6 +87,7 @@ async function calculateBasicChart(chart, value, _ref) {
|
|
|
87
87
|
BaseUtils.sortCharts(results, groupbyColumn, 'name'); // sortby statistic label
|
|
88
88
|
}
|
|
89
89
|
results.forEach(item => {
|
|
90
|
+
item.group_name = item.name;
|
|
90
91
|
item.name = getFormattedLabel(groupbyColumn, item.name, value.collaborators);
|
|
91
92
|
});
|
|
92
93
|
if ([CHART_TYPE.HEAT_MAP].includes(chart.type)) {
|
|
@@ -32,7 +32,8 @@ const calculatorMap = {
|
|
|
32
32
|
[CHART_TYPE.MIRROR]: MirrorCalculator,
|
|
33
33
|
[CHART_TYPE.BASIC_NUMBER_CARD]: CardCalculator,
|
|
34
34
|
[CHART_TYPE.TREND]: TrendCalculator,
|
|
35
|
-
[CHART_TYPE.DASHBOARD]: DashboardCalculator
|
|
35
|
+
[CHART_TYPE.DASHBOARD]: DashboardCalculator,
|
|
36
|
+
[CHART_TYPE.FUNNEL]: BasicChartCalculator
|
|
36
37
|
};
|
|
37
38
|
class OriginalDataUtils {}
|
|
38
39
|
_OriginalDataUtils = OriginalDataUtils;
|
|
@@ -1474,6 +1474,7 @@ SQLStatisticsUtils.sqlResult2JavaScript = (chart, sqlRows, chartSQLMap, columnMa
|
|
|
1474
1474
|
case CHART_TYPE.LINE:
|
|
1475
1475
|
case CHART_TYPE.HORIZONTAL_BAR:
|
|
1476
1476
|
case CHART_TYPE.AREA:
|
|
1477
|
+
case CHART_TYPE.FUNNEL:
|
|
1477
1478
|
{
|
|
1478
1479
|
return {
|
|
1479
1480
|
result: _SQLStatisticsUtils.basicChartSQLResult2JavaScript(chart, sqlRows, chartSQLMap, columnMap, tables)
|
package/dist/utils/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cloneDeep } from 'lodash-es';
|
|
1
2
|
import { GENERIC_KEY_2_SIMILAR_KEYS, GEOLOCATION_GRANULARITY, MAP_LEVEL, MUNICIPALITIES, regions } from '../constants';
|
|
2
3
|
import intl from '../intl';
|
|
3
4
|
import { SERVER_ERROR_DISPLAY_KEY, SERVER_ERROR_MSG } from '../constants/error';
|
|
@@ -13,6 +14,28 @@ export { getDateColumnFormat, isCheckboxColumn, getColumnByKey, isStatisticMapCo
|
|
|
13
14
|
export { generatorKey } from './key-generator';
|
|
14
15
|
export { translateCalendar } from './date-translate';
|
|
15
16
|
export { isFunction } from './common-utils';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* return a deep cloned list sorted by the given list
|
|
20
|
+
*
|
|
21
|
+
* used in Funnel chart to sort single select options
|
|
22
|
+
* @param {Array} data
|
|
23
|
+
* @param {Array} x_axis_option_list
|
|
24
|
+
* @returns {Array} sortedData
|
|
25
|
+
*/
|
|
26
|
+
export const getSortedDataByGivenOrder = (data, x_axis_option_list) => {
|
|
27
|
+
const idIndexMap = {};
|
|
28
|
+
x_axis_option_list.forEach((item, index) => {
|
|
29
|
+
idIndexMap[item.id] = index;
|
|
30
|
+
});
|
|
31
|
+
let sortedData = [];
|
|
32
|
+
data.forEach(item => {
|
|
33
|
+
const index = idIndexMap[item.group_name];
|
|
34
|
+
sortedData[index] = cloneDeep(item);
|
|
35
|
+
});
|
|
36
|
+
sortedData = sortedData.filter(Boolean);
|
|
37
|
+
return sortedData;
|
|
38
|
+
};
|
|
16
39
|
export function getMapCanvasStyle(container, ratio) {
|
|
17
40
|
let cWidth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 200;
|
|
18
41
|
let cHeight = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 200;
|
|
@@ -824,7 +824,7 @@ class ChartDataSQL {
|
|
|
824
824
|
error: this.error
|
|
825
825
|
};
|
|
826
826
|
}
|
|
827
|
-
if ([CHART_TYPE.BAR, CHART_TYPE.LINE, CHART_TYPE.HORIZONTAL_BAR, CHART_TYPE.AREA].includes(this.chart_type)) {
|
|
827
|
+
if ([CHART_TYPE.BAR, CHART_TYPE.LINE, CHART_TYPE.HORIZONTAL_BAR, CHART_TYPE.AREA, CHART_TYPE.FUNNEL].includes(this.chart_type)) {
|
|
828
828
|
const sql = this._basic_statistic_2_sql();
|
|
829
829
|
return {
|
|
830
830
|
sql,
|
|
@@ -790,6 +790,7 @@ const chartColumn2SqlColumn = (chartElement, table) => {
|
|
|
790
790
|
case CHART_TYPE.HORIZONTAL_BAR:
|
|
791
791
|
case CHART_TYPE.AREA:
|
|
792
792
|
case CHART_TYPE.COMPARE_BAR:
|
|
793
|
+
case CHART_TYPE.FUNNEL:
|
|
793
794
|
{
|
|
794
795
|
return basicChartStatisticColumn2sqlColumn(chart, table);
|
|
795
796
|
}
|
|
@@ -867,6 +868,7 @@ export const getDatabaseGroupName = (statItem, selectedTable) => {
|
|
|
867
868
|
case CHART_TYPE.HORIZONTAL_BAR:
|
|
868
869
|
case CHART_TYPE.HORIZONTAL_GROUP_BAR:
|
|
869
870
|
case CHART_TYPE.STACKED_HORIZONTAL_BAR:
|
|
871
|
+
case CHART_TYPE.FUNNEL:
|
|
870
872
|
{
|
|
871
873
|
let groupby_column_key, groupby_date_granularity, groupby_geolocation_granularity;
|
|
872
874
|
const {
|
package/dist/view/wrapper/bar.js
CHANGED
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { debounce } from 'lodash-es';
|
|
4
4
|
import classNames from 'classnames';
|
|
5
|
-
import {
|
|
5
|
+
import { TYPE_COLOR_USING, CHART_STYLE_COLORS, CHART_THEME_COLOR } from '../../constants';
|
|
6
6
|
import { BaseUtils, isFunction } from '../../utils';
|
|
7
7
|
import { getLabelColor, getConvertedColorRules } from '../../utils/color-utils';
|
|
8
8
|
import intl from '../../intl';
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DataSet } from '@antv/data-set';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { BaseUtils, getSortedDataByGivenOrder } from '../../utils';
|
|
5
|
+
import { CHART_THEME_COLOR, FUNNEL_LABEL_FORMAT, FUNNEL_LABEL_POSITIONS } from '../../constants';
|
|
6
|
+
import ChartComponent from './chart-component';
|
|
7
|
+
class Funnel extends ChartComponent {
|
|
8
|
+
constructor(props) {
|
|
9
|
+
super(props);
|
|
10
|
+
this.createChart = () => {
|
|
11
|
+
const appendPadding = [17, 0, 0, 0]; // used to display value on the top
|
|
12
|
+
this.initChart(this.container, {
|
|
13
|
+
appendPadding
|
|
14
|
+
});
|
|
15
|
+
this.chart.on('interval:click', e => {
|
|
16
|
+
this.props.toggleRecords(e.data.data);
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
this.drawChart = () => {
|
|
20
|
+
const {
|
|
21
|
+
result: data,
|
|
22
|
+
chart
|
|
23
|
+
} = this.props;
|
|
24
|
+
const {
|
|
25
|
+
x_axis_option_list,
|
|
26
|
+
funnel_accumulate_values
|
|
27
|
+
} = chart.config;
|
|
28
|
+
if (!x_axis_option_list.length) return;
|
|
29
|
+
const sortedData = getSortedDataByGivenOrder(data, x_axis_option_list);
|
|
30
|
+
sortedData.total = 0;
|
|
31
|
+
sortedData.forEach(item => {
|
|
32
|
+
sortedData.total += item.value;
|
|
33
|
+
});
|
|
34
|
+
if (funnel_accumulate_values) {
|
|
35
|
+
for (let i = sortedData.length - 2; i >= 0; i--) {
|
|
36
|
+
sortedData[i].value += sortedData[i + 1].value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const {
|
|
40
|
+
DataView
|
|
41
|
+
} = DataSet;
|
|
42
|
+
const dv = new DataView().source(sortedData);
|
|
43
|
+
dv.transform({
|
|
44
|
+
type: 'map',
|
|
45
|
+
callback(row) {
|
|
46
|
+
row.percent = sortedData.total === 0 ? 0 : (row.value / sortedData.total * 100).toFixed(2);
|
|
47
|
+
return row;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
const dvData = dv.rows;
|
|
51
|
+
this.loadData(dvData);
|
|
52
|
+
this.draw(dvData);
|
|
53
|
+
this.chart.render();
|
|
54
|
+
};
|
|
55
|
+
this.draw = sortedData => {
|
|
56
|
+
const {
|
|
57
|
+
chart,
|
|
58
|
+
summaryColumn,
|
|
59
|
+
tables,
|
|
60
|
+
globalTheme
|
|
61
|
+
} = this.props;
|
|
62
|
+
const {
|
|
63
|
+
table_id,
|
|
64
|
+
y_axis_summary_type,
|
|
65
|
+
y_axis_column_key,
|
|
66
|
+
y_axis_summary_column_key,
|
|
67
|
+
y_axis_summary_method,
|
|
68
|
+
funnel_show_legend,
|
|
69
|
+
funnel_label_position,
|
|
70
|
+
funnel_label_font_size,
|
|
71
|
+
funnel_label_format,
|
|
72
|
+
funnel_show_labels,
|
|
73
|
+
funnel_show_overall_rate
|
|
74
|
+
} = chart.config;
|
|
75
|
+
const theme = CHART_THEME_COLOR[globalTheme];
|
|
76
|
+
const title = this.getTitle(tables, table_id, y_axis_summary_type, y_axis_column_key || y_axis_summary_column_key);
|
|
77
|
+
const isInside = funnel_label_position === FUNNEL_LABEL_POSITIONS.INSIDE;
|
|
78
|
+
let labelStyle;
|
|
79
|
+
if (isInside) {
|
|
80
|
+
labelStyle = {
|
|
81
|
+
position: 'middle',
|
|
82
|
+
offsetY: -(funnel_label_font_size + 5),
|
|
83
|
+
style: {
|
|
84
|
+
fontSize: funnel_label_font_size,
|
|
85
|
+
fill: '#fff'
|
|
86
|
+
},
|
|
87
|
+
labelLine: true
|
|
88
|
+
};
|
|
89
|
+
} else {
|
|
90
|
+
labelStyle = {
|
|
91
|
+
position: 'left',
|
|
92
|
+
offset: -30,
|
|
93
|
+
style: {
|
|
94
|
+
fontSize: funnel_label_font_size,
|
|
95
|
+
fill: theme.labelColor
|
|
96
|
+
},
|
|
97
|
+
labelLine: true
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const contentFormatterMap = {
|
|
101
|
+
[FUNNEL_LABEL_FORMAT.NUMBER]: obj => obj.value,
|
|
102
|
+
[FUNNEL_LABEL_FORMAT.PERCENTAGE]: obj => obj.percent + '%',
|
|
103
|
+
[FUNNEL_LABEL_FORMAT.NUMBER_AND_PERCENTAGE]: obj => "".concat(obj.value, " (").concat(obj.percent, "%)")
|
|
104
|
+
};
|
|
105
|
+
this.chart.axis(false);
|
|
106
|
+
this.chart.coordinate('rect').transpose().scale(1, -1);
|
|
107
|
+
this.chart.interval().adjust('symmetric').position('name*value').shape('funnel').color('name', ['#0050B3', '#1890FF', '#40A9FF', '#69C0FF', '#BAE7FF']).label('name*value*percent', name => {
|
|
108
|
+
if (name.length > 35) {
|
|
109
|
+
name = name.substring(0, 35) + '...';
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
content: funnel_show_labels ? "".concat(name) : ''
|
|
113
|
+
};
|
|
114
|
+
}, labelStyle).tooltip('name*value', (name, value) => {
|
|
115
|
+
return {
|
|
116
|
+
title,
|
|
117
|
+
value: BaseUtils.getSummaryValueDisplayString(summaryColumn, value, y_axis_summary_method),
|
|
118
|
+
name
|
|
119
|
+
};
|
|
120
|
+
}).animate({
|
|
121
|
+
appear: {
|
|
122
|
+
animation: 'fade-in'
|
|
123
|
+
},
|
|
124
|
+
update: {
|
|
125
|
+
annotation: 'fade-in'
|
|
126
|
+
}
|
|
127
|
+
}).state({
|
|
128
|
+
active: {
|
|
129
|
+
style: {
|
|
130
|
+
stroke: null
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
this.chart.on('beforepaint', () => {
|
|
135
|
+
this.chart.annotation().clear(true);
|
|
136
|
+
if (!funnel_show_labels) return;
|
|
137
|
+
sortedData.forEach(obj => {
|
|
138
|
+
this.chart.annotation().text({
|
|
139
|
+
top: true,
|
|
140
|
+
offsetY: isInside ? 12 : 0,
|
|
141
|
+
position: [obj.name, 'center'],
|
|
142
|
+
content: contentFormatterMap[funnel_label_format](obj),
|
|
143
|
+
style: {
|
|
144
|
+
fontSize: funnel_label_font_size,
|
|
145
|
+
stroke: null,
|
|
146
|
+
fill: '#fff',
|
|
147
|
+
textAlign: 'center'
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
if (funnel_show_overall_rate) {
|
|
152
|
+
this.chart.annotation().text({
|
|
153
|
+
top: true,
|
|
154
|
+
position: ['50.5%', '-4%'],
|
|
155
|
+
content: sortedData[sortedData.length - 1].percent + '%',
|
|
156
|
+
style: {
|
|
157
|
+
fontSize: funnel_label_font_size,
|
|
158
|
+
stroke: null,
|
|
159
|
+
fill: theme.labelColor,
|
|
160
|
+
textAlign: 'center'
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// console.log(funnel_show_overall_rate);
|
|
167
|
+
|
|
168
|
+
if (funnel_show_legend) {
|
|
169
|
+
this.setLegend('name', theme, 'top-right');
|
|
170
|
+
} else {
|
|
171
|
+
this.chart.legend(false);
|
|
172
|
+
}
|
|
173
|
+
this.setToolTip();
|
|
174
|
+
this.chart.interaction('element-active');
|
|
175
|
+
};
|
|
176
|
+
this.chart = null;
|
|
177
|
+
}
|
|
178
|
+
componentDidMount() {
|
|
179
|
+
this.createChart();
|
|
180
|
+
this.drawChart();
|
|
181
|
+
}
|
|
182
|
+
componentDidUpdate(prevProps) {
|
|
183
|
+
if (BaseUtils.shouldChartComponentUpdate(prevProps, this.props)) {
|
|
184
|
+
this.chart && this.chart.destroy();
|
|
185
|
+
this.createChart();
|
|
186
|
+
this.drawChart();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
componentWillUnmount() {
|
|
190
|
+
this.chart && this.chart.destroy();
|
|
191
|
+
}
|
|
192
|
+
render() {
|
|
193
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
194
|
+
className: classNames('sea-chart-container'),
|
|
195
|
+
ref: ref => this.container = ref
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export default Funnel;
|
|
@@ -25,6 +25,7 @@ import WorldMap from './world-map';
|
|
|
25
25
|
import HeatMap from './heat-map';
|
|
26
26
|
import Mirror from './mirror';
|
|
27
27
|
import Trend from './trend';
|
|
28
|
+
import Funnel from './funnel';
|
|
28
29
|
const Wrapper = _ref => {
|
|
29
30
|
let {
|
|
30
31
|
dtableStoreValue,
|
|
@@ -276,6 +277,12 @@ const Wrapper = _ref => {
|
|
|
276
277
|
canvasStyle: canvasStyle
|
|
277
278
|
}));
|
|
278
279
|
}
|
|
280
|
+
case CHART_TYPE.FUNNEL:
|
|
281
|
+
{
|
|
282
|
+
return /*#__PURE__*/React.createElement(Funnel, Object.assign({}, baseProps, {
|
|
283
|
+
canvasStyle: canvasStyle
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
279
286
|
default:
|
|
280
287
|
{
|
|
281
288
|
return null;
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sea-chart",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@antv/data-set": "0.11.8",
|
|
7
7
|
"@antv/g2": "4.1.46",
|
|
8
|
+
"@dnd-kit/core": "^6.1.0",
|
|
9
|
+
"@dnd-kit/modifiers": "^7.0.0",
|
|
10
|
+
"@dnd-kit/sortable": "^8.0.0",
|
|
11
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
8
12
|
"@seafile/seafile-calendar": "^0.0.24",
|
|
9
13
|
"classnames": "^2.3.2",
|
|
10
14
|
"dayjs": "1.10.7",
|