@graphenedata/cli 0.0.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/LICENSE.md +100 -0
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/cli.ts +157 -0
- package/dist/cli/cli.js +43 -0
- package/dist/docs/data_apps/components/charts/annotations.md +673 -0
- package/dist/docs/data_apps/components/charts/area-chart.md +202 -0
- package/dist/docs/data_apps/components/charts/bar-chart.md +317 -0
- package/dist/docs/data_apps/components/charts/box-plot.md +190 -0
- package/dist/docs/data_apps/components/charts/bubble-chart.md +151 -0
- package/dist/docs/data_apps/components/charts/calendar-heatmap.md +112 -0
- package/dist/docs/data_apps/components/charts/custom-echarts.md +308 -0
- package/dist/docs/data_apps/components/charts/echarts-options.md +217 -0
- package/dist/docs/data_apps/components/charts/funnel-chart.md +106 -0
- package/dist/docs/data_apps/components/charts/heatmap.md +180 -0
- package/dist/docs/data_apps/components/charts/histogram.md +107 -0
- package/dist/docs/data_apps/components/charts/line-chart.md +265 -0
- package/dist/docs/data_apps/components/charts/mixed-type-charts.md +240 -0
- package/dist/docs/data_apps/components/charts/sankey-diagram.md +301 -0
- package/dist/docs/data_apps/components/charts/scatter-plot.md +134 -0
- package/dist/docs/data_apps/components/charts/sparkline.md +68 -0
- package/dist/docs/data_apps/components/data/big-value.md +153 -0
- package/dist/docs/data_apps/components/data/delta.md +89 -0
- package/dist/docs/data_apps/components/data/table.md +470 -0
- package/dist/docs/data_apps/components/data/value.md +97 -0
- package/dist/docs/data_apps/components/inputs/button-group.md +154 -0
- package/dist/docs/data_apps/components/inputs/checkbox.md +52 -0
- package/dist/docs/data_apps/components/inputs/date-input.md +131 -0
- package/dist/docs/data_apps/components/inputs/date-range.md +124 -0
- package/dist/docs/data_apps/components/inputs/dimension-grid.md +67 -0
- package/dist/docs/data_apps/components/inputs/dropdown.md +199 -0
- package/dist/docs/data_apps/components/inputs/index.md +3 -0
- package/dist/docs/data_apps/components/inputs/slider.md +126 -0
- package/dist/docs/data_apps/components/inputs/text-input.md +86 -0
- package/dist/docs/data_apps/components/maps/area-map.md +397 -0
- package/dist/docs/data_apps/components/maps/base-map.md +269 -0
- package/dist/docs/data_apps/components/maps/bubble-map.md +361 -0
- package/dist/docs/data_apps/components/maps/point-map.md +326 -0
- package/dist/docs/data_apps/components/maps/us-map.md +167 -0
- package/dist/docs/data_apps/components/ui/accordion.md +116 -0
- package/dist/docs/data_apps/components/ui/alert.md +37 -0
- package/dist/docs/data_apps/components/ui/big-link.md +19 -0
- package/dist/docs/data_apps/components/ui/details.md +58 -0
- package/dist/docs/data_apps/components/ui/download-data.md +41 -0
- package/dist/docs/data_apps/components/ui/embed.md +47 -0
- package/dist/docs/data_apps/components/ui/grid.md +45 -0
- package/dist/docs/data_apps/components/ui/image.md +61 -0
- package/dist/docs/data_apps/components/ui/info.md +47 -0
- package/dist/docs/data_apps/components/ui/last-refreshed.md +28 -0
- package/dist/docs/data_apps/components/ui/link-button.md +20 -0
- package/dist/docs/data_apps/components/ui/link.md +40 -0
- package/dist/docs/data_apps/components/ui/modal.md +57 -0
- package/dist/docs/data_apps/components/ui/note.md +32 -0
- package/dist/docs/data_apps/components/ui/print-format-components.md +85 -0
- package/dist/docs/data_apps/components/ui/tabs.md +122 -0
- package/dist/docs/graphene.md +129 -0
- package/dist/ui/app.css +332 -0
- package/dist/ui/assets/favicon.ico +0 -0
- package/dist/ui/component-utilities/autoFormatting.js +301 -0
- package/dist/ui/component-utilities/builtInFormats.js +482 -0
- package/dist/ui/component-utilities/chartContext.js +12 -0
- package/dist/ui/component-utilities/chartWindowDebug.js +21 -0
- package/dist/ui/component-utilities/checkInputs.js +95 -0
- package/dist/ui/component-utilities/convert.js +15 -0
- package/dist/ui/component-utilities/dateParsing.js +57 -0
- package/dist/ui/component-utilities/dropdownContext.ts +1 -0
- package/dist/ui/component-utilities/echarts.js +262 -0
- package/dist/ui/component-utilities/echartsThemes.js +453 -0
- package/dist/ui/component-utilities/formatTitle.js +24 -0
- package/dist/ui/component-utilities/formatting.js +258 -0
- package/dist/ui/component-utilities/getColumnExtents.js +79 -0
- package/dist/ui/component-utilities/getColumnSummary.js +67 -0
- package/dist/ui/component-utilities/getCompletedData.js +114 -0
- package/dist/ui/component-utilities/getDistinctCount.js +7 -0
- package/dist/ui/component-utilities/getDistinctValues.js +15 -0
- package/dist/ui/component-utilities/getSeriesConfig.js +236 -0
- package/dist/ui/component-utilities/getSortedData.js +7 -0
- package/dist/ui/component-utilities/getStackPercentages.js +43 -0
- package/dist/ui/component-utilities/getStackedData.js +17 -0
- package/dist/ui/component-utilities/getYAxisIndex.js +15 -0
- package/dist/ui/component-utilities/globalContexts.js +1 -0
- package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +119 -0
- package/dist/ui/component-utilities/inputUtils.ts +25 -0
- package/dist/ui/component-utilities/replaceNulls.js +14 -0
- package/dist/ui/component-utilities/tableUtils.ts +120 -0
- package/dist/ui/component-utilities/themeStores.ts +116 -0
- package/dist/ui/components/Area.svelte +174 -0
- package/dist/ui/components/AreaChart.svelte +150 -0
- package/dist/ui/components/Bar.svelte +326 -0
- package/dist/ui/components/BarChart.svelte +194 -0
- package/dist/ui/components/BigValue.svelte +69 -0
- package/dist/ui/components/Chart.svelte +1070 -0
- package/dist/ui/components/Column.svelte +172 -0
- package/dist/ui/components/DateRange.svelte +324 -0
- package/dist/ui/components/Dropdown.svelte +738 -0
- package/dist/ui/components/DropdownOption.svelte +21 -0
- package/dist/ui/components/ECharts.svelte +77 -0
- package/dist/ui/components/ErrorChart.svelte +54 -0
- package/dist/ui/components/GrapheneQuery.svelte +12 -0
- package/dist/ui/components/InlineDelta.svelte +150 -0
- package/dist/ui/components/Line.svelte +210 -0
- package/dist/ui/components/LineChart.svelte +178 -0
- package/dist/ui/components/PieChart.svelte +36 -0
- package/dist/ui/components/QueryLoad.svelte +82 -0
- package/dist/ui/components/Row.svelte +14 -0
- package/dist/ui/components/SortIcon.svelte +32 -0
- package/dist/ui/components/Table.svelte +19 -0
- package/dist/ui/components/TableCell.svelte +75 -0
- package/dist/ui/components/TableGroupRow.svelte +136 -0
- package/dist/ui/components/TableGroupToggle.svelte +42 -0
- package/dist/ui/components/TableHeader.svelte +242 -0
- package/dist/ui/components/TableRow.svelte +283 -0
- package/dist/ui/components/TableSubtotalRow.svelte +62 -0
- package/dist/ui/components/TableTotalRow.svelte +88 -0
- package/dist/ui/components/TextInput.svelte +92 -0
- package/dist/ui/components/_Table.svelte +516 -0
- package/dist/ui/internal/clientCache.ts +43 -0
- package/dist/ui/internal/queryEngine.ts +169 -0
- package/dist/ui/internal/telemetry.ts +28 -0
- package/dist/ui/internal/theme.ts +88 -0
- package/dist/ui/layout.svelte +3 -0
- package/dist/ui/playwright.config.ts +30 -0
- package/dist/ui/web.js +106 -0
- package/package.json +71 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import chroma from 'chroma-js'
|
|
3
|
+
import InlineDelta from './InlineDelta.svelte'
|
|
4
|
+
import TableCell from './TableCell.svelte'
|
|
5
|
+
import {safeExtractColumn} from '../component-utilities/tableUtils'
|
|
6
|
+
import {formatValue, getFormatObjectFromString} from '../component-utilities/formatting.js'
|
|
7
|
+
import {getThemeStores} from '../component-utilities/themeStores'
|
|
8
|
+
|
|
9
|
+
export let displayedData: any[] = []
|
|
10
|
+
export let rowShading: boolean | string | undefined = undefined
|
|
11
|
+
export let link: string | undefined = undefined
|
|
12
|
+
export let rowNumbers: boolean | string | undefined = undefined
|
|
13
|
+
export let rowLines: boolean | string | undefined = undefined
|
|
14
|
+
export let index = 0
|
|
15
|
+
export let columnSummary: any[] = []
|
|
16
|
+
export let grouped = false
|
|
17
|
+
export let groupType: 'accordion' | 'section' | undefined = undefined
|
|
18
|
+
export let groupColumn: string | undefined = undefined
|
|
19
|
+
export let rowSpan = 1
|
|
20
|
+
export let groupNamePosition: 'top' | 'middle' | 'bottom' = 'middle'
|
|
21
|
+
export let orderedColumns: any[] = []
|
|
22
|
+
export let compact: boolean | string | undefined = undefined
|
|
23
|
+
|
|
24
|
+
const {theme} = getThemeStores()
|
|
25
|
+
|
|
26
|
+
const computeColorScale = (
|
|
27
|
+
column: any,
|
|
28
|
+
columnMin: number | undefined,
|
|
29
|
+
columnMax: number | undefined,
|
|
30
|
+
) => {
|
|
31
|
+
if (!column?.colorScale || !column?.colorScale.length) return undefined
|
|
32
|
+
if (!hasFiniteNumber(columnMin) || !hasFiniteNumber(columnMax) || columnMin === columnMax) return undefined
|
|
33
|
+
|
|
34
|
+
let rawDomain
|
|
35
|
+
if (Array.isArray(column.colorBreakpoints) && column.colorBreakpoints.length) {
|
|
36
|
+
rawDomain = column.colorBreakpoints
|
|
37
|
+
} else if (column.colorMid !== undefined && column.colorMid !== null) {
|
|
38
|
+
rawDomain = [columnMin, column.colorMid, columnMax]
|
|
39
|
+
} else {
|
|
40
|
+
rawDomain = [columnMin, columnMax]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let domain = rawDomain
|
|
44
|
+
.map((value) => (typeof value === 'string' ? Number(value) : value))
|
|
45
|
+
.filter((value) => typeof value === 'number' && Number.isFinite(value))
|
|
46
|
+
|
|
47
|
+
if (domain.length < 2) return undefined
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
return chroma.scale(column.colorScale).domain(domain)
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.warn('Unable to build color scale for column', column.id, error)
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const hasFiniteNumber = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value)
|
|
58
|
+
|
|
59
|
+
const toBool = (val: boolean | string | undefined) => {
|
|
60
|
+
if (val === undefined) return false
|
|
61
|
+
if (typeof val === 'string') {
|
|
62
|
+
let normalized = val.trim().toLowerCase()
|
|
63
|
+
if (normalized === 'true') return true
|
|
64
|
+
if (normalized === 'false') return false
|
|
65
|
+
}
|
|
66
|
+
return Boolean(val)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
rowShading = toBool(rowShading)
|
|
70
|
+
rowNumbers = toBool(rowNumbers)
|
|
71
|
+
rowLines = toBool(rowLines ?? true)
|
|
72
|
+
compact = toBool(compact)
|
|
73
|
+
|
|
74
|
+
const isExternalUrl = (url: string) => {
|
|
75
|
+
try {
|
|
76
|
+
let target = new URL(url, window.location.origin)
|
|
77
|
+
return target.origin !== window.location.origin
|
|
78
|
+
} catch {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const navigateToLink = (row: any, event: MouseEvent) => {
|
|
84
|
+
if (!link) return
|
|
85
|
+
let href = row?.[link]
|
|
86
|
+
if (!href) return
|
|
87
|
+
|
|
88
|
+
let anchorTarget = (event.target as HTMLElement | null)?.closest('a')
|
|
89
|
+
if (anchorTarget && anchorTarget.getAttribute('target') === '_blank') return
|
|
90
|
+
|
|
91
|
+
if (isExternalUrl(href)) {
|
|
92
|
+
window.open(href, '_blank', 'noopener')
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
window.location.assign(href)
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
{#each displayedData as row, i (i)}
|
|
101
|
+
{@const shaded = rowShading && i % 2 === 1}
|
|
102
|
+
{@const clickable = link && row[link]}
|
|
103
|
+
<tr
|
|
104
|
+
class="table-row"
|
|
105
|
+
class:table-row--shaded={shaded}
|
|
106
|
+
class:table-row--lined={rowLines}
|
|
107
|
+
class:table-row--clickable={clickable}
|
|
108
|
+
on:click={(event) => clickable && navigateToLink(row, event)}
|
|
109
|
+
>
|
|
110
|
+
{#if rowNumbers && groupType !== 'section'}
|
|
111
|
+
<TableCell class="index" {compact}>
|
|
112
|
+
{(index + i + 1).toLocaleString()}
|
|
113
|
+
</TableCell>
|
|
114
|
+
{/if}
|
|
115
|
+
|
|
116
|
+
{#each orderedColumns as column, k (k)}
|
|
117
|
+
{@const summary = safeExtractColumn(column, columnSummary)}
|
|
118
|
+
{@const scaleSummary = column.scaleColumn ? columnSummary.find((d) => d.id === column.scaleColumn) : summary}
|
|
119
|
+
{@const columnMin = column.colorMin ?? scaleSummary?.columnUnitSummary?.min}
|
|
120
|
+
{@const columnMax = column.colorMax ?? scaleSummary?.columnUnitSummary?.max}
|
|
121
|
+
{@const colorScale = column.contentType === 'colorscale'
|
|
122
|
+
? computeColorScale(column, columnMin, columnMax)
|
|
123
|
+
: undefined}
|
|
124
|
+
{@const rawCellColor = (() => {
|
|
125
|
+
if (!colorScale) return undefined
|
|
126
|
+
if (column.scaleColumn) return colorScale(row[column.scaleColumn])
|
|
127
|
+
return colorScale(row[column.id])
|
|
128
|
+
})()}
|
|
129
|
+
{@const formattedColor = rawCellColor ? chroma(rawCellColor).hex() : undefined}
|
|
130
|
+
{@const fontColor = (() => {
|
|
131
|
+
if (column.redNegatives && row[column.id] < 0) return $theme.colors.negative
|
|
132
|
+
if (!formattedColor) return undefined
|
|
133
|
+
let contentContrast = chroma.contrast(formattedColor, $theme.colors['base-content'])
|
|
134
|
+
let backgroundContrast = chroma.contrast(formattedColor, $theme.colors['base-100']) + 0.5
|
|
135
|
+
if (contentContrast < backgroundContrast) return $theme.colors['base-100']
|
|
136
|
+
return $theme.colors['base-content']
|
|
137
|
+
})()}
|
|
138
|
+
{@const columnFormat = (() => {
|
|
139
|
+
if (column.fmt) return getFormatObjectFromString(column.fmt, summary.format?.valueType)
|
|
140
|
+
if (column.fmtColumn && row[column.fmtColumn]) {
|
|
141
|
+
return getFormatObjectFromString(row[column.fmtColumn], summary.format?.valueType)
|
|
142
|
+
}
|
|
143
|
+
return summary.format
|
|
144
|
+
})()}
|
|
145
|
+
{@const paddingLeft = k === 0 && grouped && groupType === 'accordion' && !rowNumbers ? '28px' : undefined}
|
|
146
|
+
{@const shouldShow = !(groupType === 'section' && groupColumn === summary.id && i !== 0)}
|
|
147
|
+
<TableCell
|
|
148
|
+
class={summary?.type}
|
|
149
|
+
{compact}
|
|
150
|
+
verticalAlign={groupType === 'section' ? groupNamePosition : undefined}
|
|
151
|
+
rowSpan={groupType === 'section' && groupColumn === summary.id && i === 0 ? rowSpan : 1}
|
|
152
|
+
{paddingLeft}
|
|
153
|
+
wrap={column.wrap}
|
|
154
|
+
cellColor={formattedColor}
|
|
155
|
+
fontColor={fontColor}
|
|
156
|
+
show={shouldShow}
|
|
157
|
+
>
|
|
158
|
+
{#if column.contentType === 'image' && row[column.id] !== undefined}
|
|
159
|
+
<img
|
|
160
|
+
src={row[column.id]}
|
|
161
|
+
alt={column.alt ? row[column.alt] : String(row[column.id]).replace(/^(.*[/])/, '').replace(/[.][^.]+$/, '')}
|
|
162
|
+
class="table-image"
|
|
163
|
+
style:height={column.height}
|
|
164
|
+
style:width={column.width}
|
|
165
|
+
/>
|
|
166
|
+
{:else if column.contentType === 'link' && row[column.id] !== undefined}
|
|
167
|
+
{#if column.linkLabel != undefined && row[column.linkLabel] == undefined && column.linkLabel in row}
|
|
168
|
+
–
|
|
169
|
+
{:else}
|
|
170
|
+
{@const linkTarget = row[column.id]}
|
|
171
|
+
<a
|
|
172
|
+
href={linkTarget}
|
|
173
|
+
target={column.openInNewTab ? '_blank' : undefined}
|
|
174
|
+
class="table-link"
|
|
175
|
+
>
|
|
176
|
+
{#if column.linkLabel != undefined}
|
|
177
|
+
{#if row[column.linkLabel] != undefined}
|
|
178
|
+
{@const labelSummary = safeExtractColumn({id: column.linkLabel}, columnSummary)}
|
|
179
|
+
{formatValue(
|
|
180
|
+
row[column.linkLabel],
|
|
181
|
+
column.fmt ? getFormatObjectFromString(column.fmt, labelSummary.format?.valueType) : labelSummary.format,
|
|
182
|
+
labelSummary.columnUnitSummary,
|
|
183
|
+
)}
|
|
184
|
+
{:else}
|
|
185
|
+
{column.linkLabel}
|
|
186
|
+
{/if}
|
|
187
|
+
{:else}
|
|
188
|
+
{formatValue(
|
|
189
|
+
row[column.id],
|
|
190
|
+
column.fmt ? getFormatObjectFromString(column.fmt, summary.format?.valueType) : summary.format,
|
|
191
|
+
summary.columnUnitSummary,
|
|
192
|
+
)}
|
|
193
|
+
{/if}
|
|
194
|
+
</a>
|
|
195
|
+
{/if}
|
|
196
|
+
{:else if column.contentType === 'delta' && row[column.id] !== undefined}
|
|
197
|
+
<InlineDelta
|
|
198
|
+
value={row[column.id]}
|
|
199
|
+
downIsGood={column.downIsGood}
|
|
200
|
+
formatObject={columnFormat}
|
|
201
|
+
columnUnitSummary={summary.columnUnitSummary}
|
|
202
|
+
showValue={column.showValue}
|
|
203
|
+
showSymbol={column.deltaSymbol}
|
|
204
|
+
align={column.align}
|
|
205
|
+
neutralMin={column.neutralMin ?? 0}
|
|
206
|
+
neutralMax={column.neutralMax ?? 0}
|
|
207
|
+
chip={column.chip}
|
|
208
|
+
/>
|
|
209
|
+
{:else}
|
|
210
|
+
{#if row[column.id] === null || row[column.id] === undefined}
|
|
211
|
+
–
|
|
212
|
+
{:else if summary.type === 'number'}
|
|
213
|
+
{formatValue(row[column.id], columnFormat, summary.columnUnitSummary)}
|
|
214
|
+
{:else}
|
|
215
|
+
{formatValue(row[column.id], columnFormat, summary.columnUnitSummary)}
|
|
216
|
+
{/if}
|
|
217
|
+
{/if}
|
|
218
|
+
</TableCell>
|
|
219
|
+
{/each}
|
|
220
|
+
|
|
221
|
+
{#if link}
|
|
222
|
+
<TableCell class="table-row__chevron" show={Boolean(row[link])} align="center">
|
|
223
|
+
{#if row[link]}
|
|
224
|
+
<svg class="table-row__icon" viewBox="0 0 16 16" aria-hidden="true">
|
|
225
|
+
<path
|
|
226
|
+
d="M6.22 3.22a.75.75 0 0 1 1.06 0l4 4a.75.75 0 0 1 0 1.06l-4 4a.75.75 0 1 1-1.06-1.06L9.19 8 6.22 5.03a.75.75 0 0 1 0-1.06Z"
|
|
227
|
+
fill="currentColor"
|
|
228
|
+
fill-rule="evenodd"
|
|
229
|
+
/>
|
|
230
|
+
</svg>
|
|
231
|
+
{/if}
|
|
232
|
+
</TableCell>
|
|
233
|
+
{/if}
|
|
234
|
+
</tr>
|
|
235
|
+
{/each}
|
|
236
|
+
|
|
237
|
+
<style>
|
|
238
|
+
.table-row {
|
|
239
|
+
transition: background-color 0.15s ease-in-out;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.table-row--shaded {
|
|
243
|
+
background: rgba(229, 231, 235, 0.4);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.table-row--clickable {
|
|
247
|
+
cursor: pointer;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.table-row--clickable:hover {
|
|
251
|
+
background: rgba(229, 231, 235, 0.6);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
:global(.table-row--lined) td {
|
|
255
|
+
border-bottom: 1px solid rgba(107, 114, 128, 0.2);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.table-image {
|
|
259
|
+
display: block;
|
|
260
|
+
margin: 4px auto;
|
|
261
|
+
max-width: 100%;
|
|
262
|
+
border-radius: 0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.table-link {
|
|
266
|
+
color: var(--color-primary, #2563eb);
|
|
267
|
+
text-decoration: none;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.table-link:hover {
|
|
271
|
+
filter: brightness(1.1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
:global(.table-row__chevron) {
|
|
275
|
+
width: 24px;
|
|
276
|
+
padding-right: 6px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.table-row__icon {
|
|
280
|
+
width: 10px;
|
|
281
|
+
height: 10px;
|
|
282
|
+
}
|
|
283
|
+
</style>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import InlineDelta from './InlineDelta.svelte'
|
|
3
|
+
import {aggregateColumn, safeExtractColumn} from '../component-utilities/tableUtils'
|
|
4
|
+
import {formatValue, getFormatObjectFromString} from '../component-utilities/formatting.js'
|
|
5
|
+
import TableCell from './TableCell.svelte'
|
|
6
|
+
|
|
7
|
+
export let groupName: string | undefined = undefined
|
|
8
|
+
export let currentGroupData: any[] = []
|
|
9
|
+
export let columnSummary: any[] = []
|
|
10
|
+
export let rowColor: string | undefined = undefined
|
|
11
|
+
export let groupBy: string | undefined = undefined
|
|
12
|
+
export let groupType: 'accordion' | 'section' | undefined = undefined
|
|
13
|
+
export let fontColor: string | undefined = undefined
|
|
14
|
+
export let orderedColumns: any[] = []
|
|
15
|
+
export let compact: boolean | string | undefined = undefined
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<tr class="subtotal-row" style:background-color={rowColor} style:color={fontColor}>
|
|
19
|
+
{#each orderedColumns as column (column.id)}
|
|
20
|
+
{@const summary = safeExtractColumn(column, columnSummary)}
|
|
21
|
+
{@const baseFormat = column.fmt ? getFormatObjectFromString(column.fmt, summary.format?.valueType) : summary.format}
|
|
22
|
+
{@const format = (() => {
|
|
23
|
+
if (column.subtotalFmt) return getFormatObjectFromString(column.subtotalFmt)
|
|
24
|
+
if (column.totalFmt) return getFormatObjectFromString(column.totalFmt)
|
|
25
|
+
return baseFormat
|
|
26
|
+
})()}
|
|
27
|
+
<TableCell class={summary.type} {compact} align={column.align}>
|
|
28
|
+
{#if column.id !== groupBy}
|
|
29
|
+
{#if column.contentType === 'delta'}
|
|
30
|
+
<InlineDelta
|
|
31
|
+
value={aggregateColumn(currentGroupData, column.id, column.totalAgg, summary.type, column.weightCol)}
|
|
32
|
+
downIsGood={column.downIsGood}
|
|
33
|
+
formatObject={baseFormat}
|
|
34
|
+
columnUnitSummary={summary.columnUnitSummary}
|
|
35
|
+
showValue={column.showValue}
|
|
36
|
+
showSymbol={column.deltaSymbol}
|
|
37
|
+
align={column.align}
|
|
38
|
+
neutralMin={column.neutralMin ?? 0}
|
|
39
|
+
neutralMax={column.neutralMax ?? 0}
|
|
40
|
+
chip={column.chip}
|
|
41
|
+
/>
|
|
42
|
+
{:else}
|
|
43
|
+
{formatValue(
|
|
44
|
+
aggregateColumn(currentGroupData, column.id, column.totalAgg, summary.type, column.weightCol),
|
|
45
|
+
format,
|
|
46
|
+
summary.columnUnitSummary,
|
|
47
|
+
)}
|
|
48
|
+
{/if}
|
|
49
|
+
{:else if groupType === 'section'}
|
|
50
|
+
{groupName}
|
|
51
|
+
{/if}
|
|
52
|
+
</TableCell>
|
|
53
|
+
{/each}
|
|
54
|
+
</tr>
|
|
55
|
+
|
|
56
|
+
<style>
|
|
57
|
+
.subtotal-row {
|
|
58
|
+
border-bottom: 1px solid rgba(107, 114, 128, 0.3);
|
|
59
|
+
background: rgba(226, 232, 240, 0.6);
|
|
60
|
+
font-weight: 600;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import InlineDelta from './InlineDelta.svelte'
|
|
3
|
+
import TableCell from './TableCell.svelte'
|
|
4
|
+
import {safeExtractColumn, weightedMean} from '../component-utilities/tableUtils'
|
|
5
|
+
import {formatValue, getFormatObjectFromString} from '../component-utilities/formatting.js'
|
|
6
|
+
|
|
7
|
+
export let data: any[] = []
|
|
8
|
+
export let rowNumbers: boolean | string | undefined = undefined
|
|
9
|
+
export let columnSummary: any[] = []
|
|
10
|
+
export let rowColor: string | undefined = undefined
|
|
11
|
+
export let fontColor: string | undefined = undefined
|
|
12
|
+
export let groupType: 'accordion' | 'section' | undefined = undefined
|
|
13
|
+
export let orderedColumns: any[] = []
|
|
14
|
+
export let compact: boolean | string | undefined = undefined
|
|
15
|
+
|
|
16
|
+
const toBool = (value: boolean | string | undefined) => {
|
|
17
|
+
if (value === undefined) return false
|
|
18
|
+
if (typeof value === 'string') {
|
|
19
|
+
let normalized = value.trim().toLowerCase()
|
|
20
|
+
if (normalized === 'true') return true
|
|
21
|
+
if (normalized === 'false') return false
|
|
22
|
+
}
|
|
23
|
+
return Boolean(value)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
rowNumbers = toBool(rowNumbers)
|
|
27
|
+
compact = toBool(compact)
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<tr class="total-row" style:background-color={rowColor} style:color={fontColor}>
|
|
31
|
+
{#if rowNumbers && groupType !== 'section'}
|
|
32
|
+
<TableCell class="index" {compact} topBorder="1px solid rgba(107, 114, 128, 0.5)" />
|
|
33
|
+
{/if}
|
|
34
|
+
|
|
35
|
+
{#each orderedColumns as column (column.id)}
|
|
36
|
+
{@const summary = safeExtractColumn(column, columnSummary)}
|
|
37
|
+
{@const format = (() => {
|
|
38
|
+
if (column.totalFmt) return getFormatObjectFromString(column.totalFmt)
|
|
39
|
+
if (column.fmt) return getFormatObjectFromString(column.fmt, summary.format?.valueType)
|
|
40
|
+
return summary.format
|
|
41
|
+
})()}
|
|
42
|
+
{@const totalAgg = column.totalAgg ?? 'sum'}
|
|
43
|
+
<TableCell
|
|
44
|
+
{compact}
|
|
45
|
+
dataType={summary.type}
|
|
46
|
+
align={column.align}
|
|
47
|
+
height={column.height}
|
|
48
|
+
width={column.width}
|
|
49
|
+
wrap={column.wrap}
|
|
50
|
+
topBorder="1px solid rgba(107, 114, 128, 0.5)"
|
|
51
|
+
>
|
|
52
|
+
{#if ['sum', 'mean', 'weightedMean', 'median', 'min', 'max', 'count', 'countDistinct'].includes(totalAgg)}
|
|
53
|
+
{#if column.contentType === 'delta'}
|
|
54
|
+
<InlineDelta
|
|
55
|
+
value={totalAgg === 'weightedMean' ? weightedMean(data, column.id, column.weightCol) : summary.columnUnitSummary?.[totalAgg]}
|
|
56
|
+
downIsGood={column.downIsGood}
|
|
57
|
+
formatObject={format}
|
|
58
|
+
columnUnitSummary={summary.columnUnitSummary}
|
|
59
|
+
showValue={column.showValue}
|
|
60
|
+
showSymbol={column.deltaSymbol}
|
|
61
|
+
align={column.align}
|
|
62
|
+
neutralMin={column.neutralMin ?? 0}
|
|
63
|
+
neutralMax={column.neutralMax ?? 0}
|
|
64
|
+
chip={column.chip}
|
|
65
|
+
/>
|
|
66
|
+
{:else}
|
|
67
|
+
{formatValue(
|
|
68
|
+
totalAgg === 'weightedMean' ? weightedMean(data, column.id, column.weightCol) : summary.columnUnitSummary?.[totalAgg],
|
|
69
|
+
format,
|
|
70
|
+
summary.columnUnitSummary,
|
|
71
|
+
)}
|
|
72
|
+
{/if}
|
|
73
|
+
{:else}
|
|
74
|
+
{#if column.totalFmt}
|
|
75
|
+
{formatValue(totalAgg, format, summary.columnUnitSummary)}
|
|
76
|
+
{:else}
|
|
77
|
+
{totalAgg}
|
|
78
|
+
{/if}
|
|
79
|
+
{/if}
|
|
80
|
+
</TableCell>
|
|
81
|
+
{/each}
|
|
82
|
+
</tr>
|
|
83
|
+
|
|
84
|
+
<style>
|
|
85
|
+
.total-row {
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {onMount} from 'svelte'
|
|
3
|
+
import {toBoolean} from '../component-utilities/inputUtils'
|
|
4
|
+
|
|
5
|
+
export let name: string
|
|
6
|
+
export let title: string | undefined = undefined
|
|
7
|
+
export let label: string | undefined = undefined
|
|
8
|
+
export let description: string | undefined = undefined
|
|
9
|
+
export let placeholder: string = 'Type to search'
|
|
10
|
+
export let defaultValue: string | undefined = undefined
|
|
11
|
+
export let hideDuringPrint: boolean | string = true
|
|
12
|
+
export let unsafe: boolean | string = false
|
|
13
|
+
|
|
14
|
+
let value = defaultValue || ''
|
|
15
|
+
|
|
16
|
+
$: hidePrint = toBoolean(hideDuringPrint)
|
|
17
|
+
$: allowUnsafe = toBoolean(unsafe)
|
|
18
|
+
$: displayLabel = title || label
|
|
19
|
+
|
|
20
|
+
onMount(() => {
|
|
21
|
+
pushValue(value)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
$: if (defaultValue !== undefined && defaultValue !== value && !value) {
|
|
25
|
+
value = defaultValue
|
|
26
|
+
pushValue(value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sanitize (input: string): string {
|
|
30
|
+
if (allowUnsafe) return input
|
|
31
|
+
return input.replace(/'/g, "''")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function pushValue (input: string) {
|
|
35
|
+
let trimmed = input ?? ''
|
|
36
|
+
let _safe = sanitize(trimmed)
|
|
37
|
+
let paramValue = trimmed === '' ? null : trimmed
|
|
38
|
+
window.$GRAPHENE.updateParam(name, paramValue)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function onInput (event: Event) {
|
|
42
|
+
value = (event.currentTarget as HTMLInputElement).value
|
|
43
|
+
pushValue(value)
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<div class={`input-block${hidePrint ? ' hide-print' : ''}`}>
|
|
48
|
+
{#if displayLabel}
|
|
49
|
+
<label class="input-label" for={`text-input-${name}`}>{displayLabel}</label>
|
|
50
|
+
{/if}
|
|
51
|
+
{#if description}
|
|
52
|
+
<div class="input-description">{description}</div>
|
|
53
|
+
{/if}
|
|
54
|
+
<input
|
|
55
|
+
id={`text-input-${name}`}
|
|
56
|
+
class="text-input"
|
|
57
|
+
type="text"
|
|
58
|
+
value={value}
|
|
59
|
+
placeholder={placeholder}
|
|
60
|
+
on:input={onInput}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.input-block {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
gap: 6px;
|
|
69
|
+
margin: 8px 0;
|
|
70
|
+
}
|
|
71
|
+
@media print {
|
|
72
|
+
.hide-print {
|
|
73
|
+
display: none !important;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
.input-label {
|
|
77
|
+
font-size: 12px;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
color: var(--input-label-color, #374151);
|
|
80
|
+
}
|
|
81
|
+
.input-description {
|
|
82
|
+
font-size: 12px;
|
|
83
|
+
color: rgba(55, 65, 81, 0.8);
|
|
84
|
+
}
|
|
85
|
+
.text-input {
|
|
86
|
+
min-width: 200px;
|
|
87
|
+
padding: 6px 8px;
|
|
88
|
+
border-radius: 6px;
|
|
89
|
+
border: 1px solid rgba(107, 114, 128, 0.4);
|
|
90
|
+
font-size: 14px;
|
|
91
|
+
}
|
|
92
|
+
</style>
|