@opengis/bi 1.0.11 → 1.0.13
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/charts/bar.png +0 -0
- package/dist/assets/charts/funnel.png +0 -0
- package/dist/assets/charts/no_data.jpg +0 -0
- package/dist/assets/charts/number.png +0 -0
- package/dist/assets/charts/pie.png +0 -0
- package/dist/assets/charts/progress.png +0 -0
- package/dist/assets/charts/stat.png +0 -0
- package/dist/assets/images/bar.png +0 -0
- package/dist/assets/images/funnel.png +0 -0
- package/dist/assets/images/no_data.jpg +0 -0
- package/dist/assets/images/number.png +0 -0
- package/dist/assets/images/pie.png +0 -0
- package/dist/assets/images/progress.png +0 -0
- package/dist/assets/images/stat.png +0 -0
- package/dist/bi.js +1 -1
- package/dist/bi.umd.cjs +722 -113
- package/dist/{import-file-D6RYWvi_.js → import-file-1T7kpSzt.js} +43137 -43183
- package/dist/map-component-mixin-BLM9iEWA.js +18712 -0
- package/dist/style.css +1 -1
- package/dist/vs-calendar-WiK1hcHS.js +96 -0
- package/dist/vs-funnel-bar-CpPbYZ0_.js +92 -0
- package/dist/vs-heatmap-BG4eIROH.js +83 -0
- package/dist/vs-map-BRk6Fmks.js +66 -0
- package/dist/vs-map-cluster-Dfe9INqE.js +103 -0
- package/dist/vs-number-CJq-vi95.js +39 -0
- package/dist/{vs-text-UyIWGqQO.js → vs-text-DcrAdQ40.js} +60 -60
- package/package.json +11 -6
- package/server/migrations/bi.sql +27 -0
- package/server/plugins/hook.js +86 -0
- package/server/plugins/vite.js +7 -4
- package/server/routes/dashboard/controllers/dashboard.js +24 -4
- package/server/routes/data/controllers/data.js +48 -52
- package/server/routes/data/controllers/util/chartSQL.js +8 -7
- package/server/routes/data/controllers/util/normalizeData.js +22 -9
- package/server/routes/edit/controllers/dashboard.add.js +5 -1
- package/server/routes/edit/controllers/dashboard.edit.js +5 -1
- package/server/routes/edit/controllers/widget.add.js +23 -7
- package/server/routes/edit/controllers/widget.del.js +1 -1
- package/server/routes/edit/controllers/widget.edit.js +8 -5
- package/server/routes/map/controllers/cluster.js +75 -0
- package/server/routes/map/controllers/clusterVtile.js +143 -0
- package/server/routes/map/controllers/geojson.js +9 -23
- package/server/routes/map/controllers/map.js +57 -0
- package/server/routes/map/controllers/vtile.js +25 -32
- package/server/routes/map/index.mjs +7 -4
- package/server/utils/getWidget.js +56 -0
- package/utils.js +11 -0
- package/dist/vs-number-DKF5ptAP.js +0 -34
- package/server/templates/dashboard/erobota/bar_area.yml +0 -17
- package/server/templates/dashboard/erobota/bar_culture.yml +0 -18
- package/server/templates/dashboard/erobota/bar_grand.yml +0 -18
- package/server/templates/dashboard/erobota/count_grand.yml +0 -8
- package/server/templates/dashboard/erobota/index.yml +0 -47
- package/server/templates/dashboard/erobota/list_culture.yml +0 -12
- package/server/templates/dashboard/erobota/list_grant.yml +0 -12
- package/server/templates/dashboard/erobota/map.yml +0 -4
- package/server/templates/dashboard/erobota/pie_area.yml +0 -17
- package/server/templates/dashboard/erobota/pie_grant.yml +0 -17
- package/server/templates/dashboard/erobota/total_area.yml +0 -9
- package/server/templates/dashboard/erobota/total_grand.yml +0 -9
- package/server/templates/dashboard/sales/index.yml +0 -40
- package/server/templates/dashboard/sales/quarterly_revenue.yml +0 -19
- package/server/templates/dashboard/sales/quarterly_revenue_by_product_line.yml +0 -19
- package/server/templates/dashboard/sales/total_products_sold.yml +0 -9
- package/server/templates/dashboard/sales/total_products_sold_by_product_line.yml +0 -12
- package/server/templates/dashboard/sales/total_revenue.yml +0 -8
- package/server/templates/dashboard/sales/total_revenue_by_product_line.yml +0 -17
- package/server/templates/dashboard/sales/vehicle_sales_info.md +0 -17
- package/server/templates/dashboard/test3/index.yml +0 -29
- package/server/templates/dashboard/test3/quarterly_revenue.yml +0 -19
- package/server/templates/dashboard/test3/widget1.yml +0 -8
- package/server/templates/pt/vehicle_sales.md +0 -17
- package/server/templates/table/demo.cleaned_sales_data.table.json +0 -104
- package/server/templates/table/test.dataset.table.json +0 -16
- package/server/templates/widget/product_line.yml +0 -20
- package/server/templates/widget/test_vtile.yml +0 -7
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { getWidget } from '../../../../utils.js';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export default async function map({
|
|
7
|
+
pg, funcs = {}, query = {},
|
|
8
|
+
}) {
|
|
9
|
+
|
|
10
|
+
const { dashboard, widget } = query;
|
|
11
|
+
|
|
12
|
+
const { data, type, layers } = await getWidget({ dashboard, widget });
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if (!['map'].includes(type)) {
|
|
16
|
+
return { message: 'access restricted: invalid widget type', status: 403 };
|
|
17
|
+
}
|
|
18
|
+
if (!data?.table) {
|
|
19
|
+
return { message: 'invalid widget: param table is required', status: 400 };
|
|
20
|
+
}
|
|
21
|
+
if (!pg.pk[data?.table]) {
|
|
22
|
+
return { message: 'invalid widget: table pkey not found', status: 400 };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { q = '' } = await funcs.getFilterSQL({
|
|
26
|
+
pg, table: data?.table, filter: query.filter,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const res = {};
|
|
30
|
+
if (data?.color) {
|
|
31
|
+
const { rows = [] } = await pg.query(`select count(*), "${data.color}" as val from ${data.table} where ${data.query || '1=1'} group by "${data.color}"`);
|
|
32
|
+
Object.assign(res, { colors: rows }); // кольори для легенди
|
|
33
|
+
}
|
|
34
|
+
if (data?.metrics?.length) {
|
|
35
|
+
const metric = data?.metrics[0];
|
|
36
|
+
const q1 = `select PERCENTILE_CONT(0) WITHIN GROUP (ORDER BY "${metric}") as "0",
|
|
37
|
+
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY "${metric}") as "25",
|
|
38
|
+
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY "${metric}") as "50",
|
|
39
|
+
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY "${metric}") as "75",
|
|
40
|
+
PERCENTILE_CONT(1) WITHIN GROUP (ORDER BY "${metric}") as "100" from ${data.table} where ${data.query || '1=1'} and ${q || '1=1'}`;
|
|
41
|
+
const sizes = await pg.query(q1).then((res1) => Object.values(res1.rows?.[0] || {}));
|
|
42
|
+
Object.assign(res, { sizes }); // розміри для легенди
|
|
43
|
+
}
|
|
44
|
+
const { bbox } = await pg.query(`select st_asgeojson(box2d(geom))::json as bbox from ${data.table} where ${data.query || '1=1'}`).then((res1) => res1.rows?.[0] || {});
|
|
45
|
+
Object.assign(res, {
|
|
46
|
+
|
|
47
|
+
layers,
|
|
48
|
+
columns: data.columns,
|
|
49
|
+
bbox, // Map bounds
|
|
50
|
+
|
|
51
|
+
top: [], // 10 найкращих
|
|
52
|
+
bottom: [], // 10 найгірших
|
|
53
|
+
});
|
|
54
|
+
return res;
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
}
|
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { createHash } from 'crypto';
|
|
5
5
|
import { writeFile, mkdir } from 'fs/promises';
|
|
6
6
|
import { existsSync, /*readdirSync, */ readFileSync } from 'fs';
|
|
7
|
+
import { getWidget } from '../../../../utils.js';
|
|
7
8
|
|
|
8
9
|
import yaml from '../../dashboard/controllers/utils/yaml.js';
|
|
9
10
|
import normalizeData from '../../data/controllers/util/normalizeData.js';
|
|
@@ -34,9 +35,9 @@ export default async function vtile(req, reply) {
|
|
|
34
35
|
pg, params = {}, query = {}, funcs = {}, log,
|
|
35
36
|
} = req;
|
|
36
37
|
|
|
37
|
-
const { filter, widget, sql, cluster, type, nocache, geom = 'geom', pointZoom = 0 } = query;
|
|
38
|
+
const { filter, widget, dashboard, sql, cluster, type, nocache, geom = 'geom', pointZoom = 0 } = query;
|
|
39
|
+
|
|
38
40
|
|
|
39
|
-
const { config = {} } = funcs;
|
|
40
41
|
|
|
41
42
|
if (!widget) {
|
|
42
43
|
return { message: 'not enough params: widget', status: 400 };
|
|
@@ -49,18 +50,11 @@ export default async function vtile(req, reply) {
|
|
|
49
50
|
return { message: 'not enough params: xyz', status: 400 };
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
const
|
|
53
|
-
const widgetDir = path.join(cwd, 'server/templates/widget');
|
|
54
|
-
|
|
55
|
-
const filePath = path.join(widgetDir, widget + '.yml');
|
|
56
|
-
|
|
57
|
-
if (!existsSync(filePath)) {
|
|
58
|
-
return { message: { root: config?.local ? widgetDir : undefined, error: `not found`, widget }, status: 404 };
|
|
59
|
-
}
|
|
53
|
+
const { data } = await getWidget({ widget, dashboard })
|
|
60
54
|
|
|
61
55
|
const headers = {
|
|
62
56
|
'Content-Type': 'application/x-protobuf',
|
|
63
|
-
'Cache-Control': nocache || sql
|
|
57
|
+
'Cache-Control': nocache || sql
|
|
64
58
|
? 'no-cache'
|
|
65
59
|
: 'public, max-age=86400',
|
|
66
60
|
};
|
|
@@ -71,12 +65,7 @@ export default async function vtile(req, reply) {
|
|
|
71
65
|
const file = path.join(root, `/map/vtile/${widget}/${hash ? `${createHash('sha1').update(hash).digest('base64')}/` : ''}${z}/${x}/${y}.mvt`);
|
|
72
66
|
|
|
73
67
|
try {
|
|
74
|
-
const fileData = readFileSync(filePath, 'utf-8');
|
|
75
|
-
const { data = {} } = filePath.includes('.yml') ? yaml.loadSafe(fileData) : {};
|
|
76
68
|
|
|
77
|
-
if (!data?.table) {
|
|
78
|
-
return { message: `invalid ${widget ? 'widget' : 'dashboard'}: table not specified`, status: 400 };
|
|
79
|
-
}
|
|
80
69
|
|
|
81
70
|
const pkey = pg.pk?.[data?.table];
|
|
82
71
|
if (!pkey) {
|
|
@@ -84,15 +73,18 @@ export default async function vtile(req, reply) {
|
|
|
84
73
|
}
|
|
85
74
|
|
|
86
75
|
// data param
|
|
87
|
-
const { table, where = '1=1', xName = {} } = normalizeData(data, query);
|
|
76
|
+
const { table, where = '1=1', xName = {}, metric } = normalizeData(data, query);
|
|
88
77
|
|
|
89
|
-
if (!xName?.name) {
|
|
90
|
-
return { message: `invalid ${widget ? 'widget' : 'dashboard'}: x axis column not specified`, status: 400 };
|
|
91
|
-
}
|
|
92
78
|
|
|
93
79
|
// get sql
|
|
94
|
-
const
|
|
95
|
-
const
|
|
80
|
+
const columns = data.columns?.map(el => el.name || el)?.join(',') || '1';
|
|
81
|
+
const filterQ = filter ? await funcs.getFilterSQL({ pg, table, filter }) : undefined;
|
|
82
|
+
const q = `select "${pkey}",
|
|
83
|
+
${data?.color ? `"${data?.color}"` : '0'} as x,
|
|
84
|
+
${data.metrics?.[0] ? `"${data.metrics[0]}"::float` : '0'} as metric,
|
|
85
|
+
${columns},
|
|
86
|
+
${geom} as geom
|
|
87
|
+
from ${filterQ ? `(${filterQ})` : table} q where ${where}`;
|
|
96
88
|
|
|
97
89
|
if (sql === '1') return q;
|
|
98
90
|
|
|
@@ -100,17 +92,18 @@ export default async function vtile(req, reply) {
|
|
|
100
92
|
10: 0.1, 11: 0.05, 12: 0.005, 13: 0.0002, 14: 0.00005,
|
|
101
93
|
}[z] || 0.000001;
|
|
102
94
|
|
|
103
|
-
const geomCol = parseInt(z, 10) < parseInt(pointZoom, 10)
|
|
95
|
+
const geomCol = parseInt(z, 10) < parseInt(pointZoom, 10) || true
|
|
104
96
|
? `ST_Centroid(${geom})`
|
|
105
97
|
: geom;
|
|
106
|
-
|
|
98
|
+
|
|
107
99
|
const bbox = mercator.bbox(+y, +x, +z, false/* , '900913' */);
|
|
108
100
|
const bbox2d = `'BOX(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d`;
|
|
109
101
|
|
|
110
102
|
const areaZoom = area[z] && false ? ` and (st_area(st_transform(${geom},3857)))>'${area[z]}'` : '';
|
|
111
103
|
|
|
104
|
+
|
|
112
105
|
const q1 = cluster > z
|
|
113
|
-
? `SELECT ST_AsMVT(q, '
|
|
106
|
+
? `SELECT ST_AsMVT(q, 'bi', 4096, 'geom','row') as tile
|
|
114
107
|
FROM (
|
|
115
108
|
SELECT floor(random() * 100000 + 1)::int + row_number() over() as row, count(*) as point_count,
|
|
116
109
|
ST_AsMVTGeom(st_transform(st_centroid(ST_Union(geom)),3857),ST_TileEnvelope(${z},${y},${x})::box2d,4096,256,false) as geom
|
|
@@ -122,14 +115,15 @@ export default async function vtile(req, reply) {
|
|
|
122
115
|
ORDER BY 1 DESC
|
|
123
116
|
)q`
|
|
124
117
|
|
|
125
|
-
: `SELECT ST_AsMVT(q, '
|
|
118
|
+
: `SELECT ST_AsMVT(q, 'bi', 4096, 'geom','row') as tile
|
|
126
119
|
FROM (
|
|
127
120
|
SELECT
|
|
128
121
|
floor(random() * 100000 + 1)::int + row_number() over() as row,
|
|
129
122
|
|
|
130
123
|
${pkey} as id,
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
x,
|
|
125
|
+
metric,
|
|
126
|
+
${columns},
|
|
133
127
|
ST_AsMVTGeom(st_transform(${geomCol}, 3857),ST_TileEnvelope(${z},${y},${x})::box2d,4096,256,false) as geom
|
|
134
128
|
|
|
135
129
|
FROM (select * from (${q})q where ${geom} && ${bbox2d}
|
|
@@ -144,9 +138,8 @@ export default async function vtile(req, reply) {
|
|
|
144
138
|
) q`;
|
|
145
139
|
|
|
146
140
|
if (sql === '2') return q1;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
funcs.autoIndex({ table, columns: [xName.name] });
|
|
141
|
+
|
|
142
|
+
|
|
150
143
|
|
|
151
144
|
const { rows = [] } = await pg.query(q1);
|
|
152
145
|
|
|
@@ -154,7 +147,7 @@ export default async function vtile(req, reply) {
|
|
|
154
147
|
|
|
155
148
|
const buffer = Buffer.concat(rows.map((el) => Buffer.from(el.tile)));
|
|
156
149
|
|
|
157
|
-
if (!nocache
|
|
150
|
+
if (!nocache) {
|
|
158
151
|
await mkdir(path.dirname(file), { recursive: true });
|
|
159
152
|
await writeFile(file, buffer, 'binary');
|
|
160
153
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import map from './controllers/map.js';
|
|
3
2
|
import geojson from './controllers/geojson.js';
|
|
4
3
|
import vtile from './controllers/vtile.js';
|
|
5
4
|
|
|
5
|
+
import cluster from './controllers/cluster.js';
|
|
6
|
+
import clusterVtile from './controllers/clusterVtile.js';
|
|
7
|
+
|
|
6
8
|
const biSchema = {
|
|
7
9
|
querystring: {
|
|
8
10
|
widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
@@ -14,9 +16,10 @@ const biSchema = {
|
|
|
14
16
|
},
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
|
|
18
19
|
export default async function route(fastify, opts) {
|
|
19
|
-
|
|
20
|
+
fastify.get('/bi-map', { schema: biSchema }, map);
|
|
20
21
|
fastify.get('/bi-geojson', { schema: biSchema }, geojson);
|
|
21
22
|
fastify.get('/bi-vtile/:z/:y/:x', { schema: biSchema }, vtile);
|
|
23
|
+
fastify.get('/bi-cluster', { schema: biSchema }, cluster);
|
|
24
|
+
fastify.get('/bi-cluster-vtile/:z/:y/:x', { schema: biSchema }, clusterVtile);
|
|
22
25
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getTemplate } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
import pgClients from '@opengis/fastify-table/pg/pgClients.js'
|
|
3
|
+
const pg = pgClients.client;
|
|
4
|
+
|
|
5
|
+
async function getWidget({ dashboard, widget }) {
|
|
6
|
+
|
|
7
|
+
const dashboardData = dashboard ? await getTemplate('dashboard', dashboard) : null;
|
|
8
|
+
|
|
9
|
+
const { id, tableName } = !dashboardData && pg.pk['bi.dashboard'] && dashboard ?
|
|
10
|
+
await pg.query(`select dashboard_id as id, table_name as "tableName" from bi.dashboard where $1 in (dashboard_id, name)`, [dashboard]).then((res1) => res1.rows?.[0] || {}) : {};
|
|
11
|
+
|
|
12
|
+
if (!dashboardData && dashboard && !id) {
|
|
13
|
+
return { message: `dashboard not found: ${dashboard}`, status: 404 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
dashboardData?.forEach(el => {
|
|
17
|
+
el[2] = el[0].split('.')[0]
|
|
18
|
+
})
|
|
19
|
+
const dashboardIndex = dashboardData?.find(el => el[2] == 'index')?.[1];
|
|
20
|
+
|
|
21
|
+
const widgetData = dashboard ? dashboardData?.find(el => el[2] === (widget || 'index'))?.[1] : await getTemplate('widget', widget);
|
|
22
|
+
if (typeof widgetData === 'string') {
|
|
23
|
+
return { source: widgetData, status: 200 }
|
|
24
|
+
}
|
|
25
|
+
if (!id && !dashboardData && !widgetData) {
|
|
26
|
+
return { message: `not found ${widget} ${dashboard}`, status: 404 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const q = `select *, coalesce(data::jsonb, '{}'::jsonb) || jsonb_build_object('table', table_name) as data from bi.widget where dashboard_id=$1 and name=$2`;
|
|
30
|
+
|
|
31
|
+
const { type, text, data = {}, controls, style, options } = widgetData || await pg.query(q, [id || dashboard, widget]).then((res1) => res1.rows?.[0] || {});
|
|
32
|
+
|
|
33
|
+
if (!type) {
|
|
34
|
+
return { message: `widget not found: ${widget}`, status: 404 };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Object.assign(data, {
|
|
38
|
+
table: data.table
|
|
39
|
+
|| tableName
|
|
40
|
+
|| widgetData?.table_name
|
|
41
|
+
|| dashboardIndex?.table
|
|
42
|
+
|| dashboardIndex?.table_name,
|
|
43
|
+
db: dashboardIndex?.db || pgClients.client?.options?.database,
|
|
44
|
+
});
|
|
45
|
+
const main = { ...dashboardIndex || {}, ...widgetData, ...data };
|
|
46
|
+
|
|
47
|
+
if (!main?.table) {
|
|
48
|
+
return { message: /* json.error || */ `invalid ${widget ? 'widget' : 'dashboard'}: 1`, status: 404 };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const tableSQL = main?.sql?.map((el, i) => `left join lateral(${el})t${i + 1} on 1=1`);
|
|
52
|
+
|
|
53
|
+
return { ...main, tableSQL, data, type, text, controls, style, options };
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
export default getWidget;
|
package/utils.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file contains code that we reuse
|
|
2
|
+
// between our tests.
|
|
3
|
+
|
|
4
|
+
// import getTemplatePath from '@opengis/fastify-table/table/controllers/utils/getTemplatePath.js';
|
|
5
|
+
import getWidget from './server/utils/getWidget.js';
|
|
6
|
+
|
|
7
|
+
export default null;
|
|
8
|
+
export {
|
|
9
|
+
// getTemplatePath,
|
|
10
|
+
getWidget
|
|
11
|
+
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { _ as t, f as o } from "./import-file-D6RYWvi_.js";
|
|
2
|
-
import { openBlock as s, createElementBlock as n, toDisplayString as m } from "vue";
|
|
3
|
-
const c = {
|
|
4
|
-
props: ["source", "dimensions"],
|
|
5
|
-
data() {
|
|
6
|
-
return {
|
|
7
|
-
number: ""
|
|
8
|
-
};
|
|
9
|
-
},
|
|
10
|
-
computed: {
|
|
11
|
-
formattedNumber() {
|
|
12
|
-
return o(this.number);
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
mounted() {
|
|
16
|
-
this.getNumber();
|
|
17
|
-
},
|
|
18
|
-
methods: {
|
|
19
|
-
async getNumber() {
|
|
20
|
-
try {
|
|
21
|
-
this.number = this.source[0][this.dimensions[0]];
|
|
22
|
-
} catch (e) {
|
|
23
|
-
console.error(e);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}, a = { class: "font-[Inter,_Helvetica,_Arial] p-4 w-full text-[28px] pt-[10px] min-h-[130px] border-0" };
|
|
28
|
-
function i(e, u, p, d, f, r) {
|
|
29
|
-
return s(), n("div", a, " $" + m(r.formattedNumber), 1);
|
|
30
|
-
}
|
|
31
|
-
const b = /* @__PURE__ */ t(c, [["render", i]]);
|
|
32
|
-
export {
|
|
33
|
-
b as default
|
|
34
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
type: bar
|
|
2
|
-
text: Quarterly Revenue (By Product Line)
|
|
3
|
-
|
|
4
|
-
data:
|
|
5
|
-
table: demo.parcel_object # Назва таблиці
|
|
6
|
-
query: 1=1 # Запит
|
|
7
|
-
|
|
8
|
-
metrics: # Групування
|
|
9
|
-
- name: obj_area
|
|
10
|
-
operator: sum
|
|
11
|
-
title: obj_area
|
|
12
|
-
|
|
13
|
-
x: region
|
|
14
|
-
|
|
15
|
-
controls:
|
|
16
|
-
style:
|
|
17
|
-
stack: true
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
type: bar
|
|
2
|
-
text: Розподіл культур за регіонами
|
|
3
|
-
|
|
4
|
-
data:
|
|
5
|
-
table: demo.parcel_object_culture # Назва таблиці
|
|
6
|
-
query: 1=1 # Запит
|
|
7
|
-
|
|
8
|
-
metrics: # Групування
|
|
9
|
-
- name: obj_area
|
|
10
|
-
operator: sum
|
|
11
|
-
title: obj_area
|
|
12
|
-
|
|
13
|
-
x: region
|
|
14
|
-
groupby: culture
|
|
15
|
-
|
|
16
|
-
controls:
|
|
17
|
-
style:
|
|
18
|
-
stack: true
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
type: bar
|
|
2
|
-
text: Сума виплачених грантів
|
|
3
|
-
|
|
4
|
-
data:
|
|
5
|
-
table: demo.parcel_object # Назва таблиці
|
|
6
|
-
query: 1=1 # Запит
|
|
7
|
-
|
|
8
|
-
metrics: # Групування
|
|
9
|
-
- name: grant_sum
|
|
10
|
-
operator: sum
|
|
11
|
-
title: grant_sum
|
|
12
|
-
|
|
13
|
-
x: region
|
|
14
|
-
orderby: region
|
|
15
|
-
|
|
16
|
-
controls:
|
|
17
|
-
style:
|
|
18
|
-
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
title: Гранти на розвиток садівництва та тепличного господарства
|
|
2
|
-
description: Ініціатива реалізована в межах Програми USAID з аграрного і сільського розвитку (АГРО)
|
|
3
|
-
table_name: demo.parcel
|
|
4
|
-
panels:
|
|
5
|
-
- type: column
|
|
6
|
-
col: 4
|
|
7
|
-
widgets:
|
|
8
|
-
- total_area
|
|
9
|
-
|
|
10
|
-
- widget: total_grand
|
|
11
|
-
col: 4
|
|
12
|
-
|
|
13
|
-
- widget: count_grand
|
|
14
|
-
col: 4
|
|
15
|
-
|
|
16
|
-
- widget: pie_area
|
|
17
|
-
col: 4
|
|
18
|
-
|
|
19
|
-
- widget: bar_area
|
|
20
|
-
col: 8
|
|
21
|
-
|
|
22
|
-
- widget: pie_grant
|
|
23
|
-
col: 4
|
|
24
|
-
|
|
25
|
-
- widget: bar_grand
|
|
26
|
-
col: 8
|
|
27
|
-
|
|
28
|
-
- widget: bar_culture
|
|
29
|
-
col: 12
|
|
30
|
-
|
|
31
|
-
- widget: list_culture
|
|
32
|
-
col: 6
|
|
33
|
-
|
|
34
|
-
- widget: list_grant
|
|
35
|
-
col: 6
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
style:
|
|
41
|
-
color:
|
|
42
|
-
stack: true # only for bar
|
|
43
|
-
orientation: vertical # only for bar, line
|
|
44
|
-
date_format: smart_date # d3 format number
|
|
45
|
-
number_format: smart_date # d3 format number
|
|
46
|
-
label: fff
|
|
47
|
-
tooltip: 333
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
type: pie
|
|
2
|
-
text: Площа, га
|
|
3
|
-
|
|
4
|
-
data:
|
|
5
|
-
table: demo.parcel_object # Назва таблиці
|
|
6
|
-
query: 1=1 # Запит
|
|
7
|
-
|
|
8
|
-
metrics: # Групування
|
|
9
|
-
- name: obj_area
|
|
10
|
-
operator: sum
|
|
11
|
-
title: obj_area
|
|
12
|
-
x: object_type
|
|
13
|
-
|
|
14
|
-
style:
|
|
15
|
-
donut: true
|
|
16
|
-
innerRadius: 30
|
|
17
|
-
outerRadius: 70
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
type: pie
|
|
2
|
-
text: Виплата грантів
|
|
3
|
-
|
|
4
|
-
data:
|
|
5
|
-
table: demo.parcel_object # Назва таблиці
|
|
6
|
-
query: 1=1 # Запит
|
|
7
|
-
|
|
8
|
-
metrics: # Групування
|
|
9
|
-
- name: grant_sum
|
|
10
|
-
operator: sum
|
|
11
|
-
title: grant_sum
|
|
12
|
-
x: object_type
|
|
13
|
-
|
|
14
|
-
style:
|
|
15
|
-
donut: true
|
|
16
|
-
innerRadius: 30
|
|
17
|
-
outerRadius: 70
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
title: Sales Dashboard
|
|
2
|
-
description: This example dashboard provides insight into the business operations of vehicle seller
|
|
3
|
-
table_name: demo.cleaned_sales_data
|
|
4
|
-
panels:
|
|
5
|
-
# row 1
|
|
6
|
-
- type: column
|
|
7
|
-
col: 3
|
|
8
|
-
widgets:
|
|
9
|
-
- total_revenue
|
|
10
|
-
- total_products_sold
|
|
11
|
-
|
|
12
|
-
- widget: quarterly_revenue
|
|
13
|
-
col: 6
|
|
14
|
-
- widget: vehicle_sales_info
|
|
15
|
-
col: 3
|
|
16
|
-
|
|
17
|
-
# row 2
|
|
18
|
-
- widget: total_products_sold_by_product_line
|
|
19
|
-
col: 3
|
|
20
|
-
- widget: quarterly_revenue_by_product_line
|
|
21
|
-
col: 6
|
|
22
|
-
- widget: total_revenue_by_product_line
|
|
23
|
-
col: 3
|
|
24
|
-
|
|
25
|
-
grid: # сітка якщо є
|
|
26
|
-
- { "x": 0, "y": 0, "w": 2, "h": 2, "i": 0, widget: "quarterly_revenue" }
|
|
27
|
-
- { "x": 1, "y": 0, "w": 2, "h": 2, "i": 0, widget: "total_revenue" }
|
|
28
|
-
|
|
29
|
-
widgets: null # самі віджети
|
|
30
|
-
|
|
31
|
-
filters: null # фільтри
|
|
32
|
-
|
|
33
|
-
style:
|
|
34
|
-
color:
|
|
35
|
-
stack: true # only for bar
|
|
36
|
-
orientation: vertical # only for bar, line
|
|
37
|
-
date_format: smart_date # d3 format number
|
|
38
|
-
number_format: smart_date # d3 format number
|
|
39
|
-
label: 222
|
|
40
|
-
tooltip: 333
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
type: bar
|
|
2
|
-
text: Quarterly Revenue
|
|
3
|
-
|
|
4
|
-
data:
|
|
5
|
-
table: demo.cleaned_sales_data # Назва таблиці
|
|
6
|
-
query: 1=1 # Запит
|
|
7
|
-
|
|
8
|
-
metrics: # Групування
|
|
9
|
-
- name: sales
|
|
10
|
-
operator: sum
|
|
11
|
-
title: sales
|
|
12
|
-
|
|
13
|
-
x: order_date
|
|
14
|
-
granularity: quarter
|
|
15
|
-
orderby: year
|
|
16
|
-
|
|
17
|
-
controls:
|
|
18
|
-
style:
|
|
19
|
-
horizontal: false
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
type: bar
|
|
2
|
-
text: Quarterly Revenue (By Product Line)
|
|
3
|
-
|
|
4
|
-
data:
|
|
5
|
-
table: demo.cleaned_sales_data # Назва таблиці
|
|
6
|
-
query: 1=1 # Запит
|
|
7
|
-
|
|
8
|
-
metrics: # Групування
|
|
9
|
-
- name: sales
|
|
10
|
-
operator: sum
|
|
11
|
-
title: sales
|
|
12
|
-
|
|
13
|
-
x: order_date
|
|
14
|
-
granularity: quarter
|
|
15
|
-
groupby: product_line
|
|
16
|
-
|
|
17
|
-
controls:
|
|
18
|
-
style:
|
|
19
|
-
stack: true
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
type: pie
|
|
2
|
-
text: Total Revenue (By Product Line)
|
|
3
|
-
|
|
4
|
-
data:
|
|
5
|
-
table: demo.cleaned_sales_data # Назва таблиці
|
|
6
|
-
query: 1=1 # Запит
|
|
7
|
-
|
|
8
|
-
metrics: # Групування
|
|
9
|
-
- name: sales
|
|
10
|
-
operator: sum
|
|
11
|
-
title: sales
|
|
12
|
-
x: product_line
|
|
13
|
-
|
|
14
|
-
style:
|
|
15
|
-
donut: true
|
|
16
|
-
innerRadius: 30
|
|
17
|
-
outerRadius: 70
|