@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.
Files changed (123) hide show
  1. package/LICENSE.md +100 -0
  2. package/THIRD_PARTY_NOTICES.md +12 -0
  3. package/cli.ts +157 -0
  4. package/dist/cli/cli.js +43 -0
  5. package/dist/docs/data_apps/components/charts/annotations.md +673 -0
  6. package/dist/docs/data_apps/components/charts/area-chart.md +202 -0
  7. package/dist/docs/data_apps/components/charts/bar-chart.md +317 -0
  8. package/dist/docs/data_apps/components/charts/box-plot.md +190 -0
  9. package/dist/docs/data_apps/components/charts/bubble-chart.md +151 -0
  10. package/dist/docs/data_apps/components/charts/calendar-heatmap.md +112 -0
  11. package/dist/docs/data_apps/components/charts/custom-echarts.md +308 -0
  12. package/dist/docs/data_apps/components/charts/echarts-options.md +217 -0
  13. package/dist/docs/data_apps/components/charts/funnel-chart.md +106 -0
  14. package/dist/docs/data_apps/components/charts/heatmap.md +180 -0
  15. package/dist/docs/data_apps/components/charts/histogram.md +107 -0
  16. package/dist/docs/data_apps/components/charts/line-chart.md +265 -0
  17. package/dist/docs/data_apps/components/charts/mixed-type-charts.md +240 -0
  18. package/dist/docs/data_apps/components/charts/sankey-diagram.md +301 -0
  19. package/dist/docs/data_apps/components/charts/scatter-plot.md +134 -0
  20. package/dist/docs/data_apps/components/charts/sparkline.md +68 -0
  21. package/dist/docs/data_apps/components/data/big-value.md +153 -0
  22. package/dist/docs/data_apps/components/data/delta.md +89 -0
  23. package/dist/docs/data_apps/components/data/table.md +470 -0
  24. package/dist/docs/data_apps/components/data/value.md +97 -0
  25. package/dist/docs/data_apps/components/inputs/button-group.md +154 -0
  26. package/dist/docs/data_apps/components/inputs/checkbox.md +52 -0
  27. package/dist/docs/data_apps/components/inputs/date-input.md +131 -0
  28. package/dist/docs/data_apps/components/inputs/date-range.md +124 -0
  29. package/dist/docs/data_apps/components/inputs/dimension-grid.md +67 -0
  30. package/dist/docs/data_apps/components/inputs/dropdown.md +199 -0
  31. package/dist/docs/data_apps/components/inputs/index.md +3 -0
  32. package/dist/docs/data_apps/components/inputs/slider.md +126 -0
  33. package/dist/docs/data_apps/components/inputs/text-input.md +86 -0
  34. package/dist/docs/data_apps/components/maps/area-map.md +397 -0
  35. package/dist/docs/data_apps/components/maps/base-map.md +269 -0
  36. package/dist/docs/data_apps/components/maps/bubble-map.md +361 -0
  37. package/dist/docs/data_apps/components/maps/point-map.md +326 -0
  38. package/dist/docs/data_apps/components/maps/us-map.md +167 -0
  39. package/dist/docs/data_apps/components/ui/accordion.md +116 -0
  40. package/dist/docs/data_apps/components/ui/alert.md +37 -0
  41. package/dist/docs/data_apps/components/ui/big-link.md +19 -0
  42. package/dist/docs/data_apps/components/ui/details.md +58 -0
  43. package/dist/docs/data_apps/components/ui/download-data.md +41 -0
  44. package/dist/docs/data_apps/components/ui/embed.md +47 -0
  45. package/dist/docs/data_apps/components/ui/grid.md +45 -0
  46. package/dist/docs/data_apps/components/ui/image.md +61 -0
  47. package/dist/docs/data_apps/components/ui/info.md +47 -0
  48. package/dist/docs/data_apps/components/ui/last-refreshed.md +28 -0
  49. package/dist/docs/data_apps/components/ui/link-button.md +20 -0
  50. package/dist/docs/data_apps/components/ui/link.md +40 -0
  51. package/dist/docs/data_apps/components/ui/modal.md +57 -0
  52. package/dist/docs/data_apps/components/ui/note.md +32 -0
  53. package/dist/docs/data_apps/components/ui/print-format-components.md +85 -0
  54. package/dist/docs/data_apps/components/ui/tabs.md +122 -0
  55. package/dist/docs/graphene.md +129 -0
  56. package/dist/ui/app.css +332 -0
  57. package/dist/ui/assets/favicon.ico +0 -0
  58. package/dist/ui/component-utilities/autoFormatting.js +301 -0
  59. package/dist/ui/component-utilities/builtInFormats.js +482 -0
  60. package/dist/ui/component-utilities/chartContext.js +12 -0
  61. package/dist/ui/component-utilities/chartWindowDebug.js +21 -0
  62. package/dist/ui/component-utilities/checkInputs.js +95 -0
  63. package/dist/ui/component-utilities/convert.js +15 -0
  64. package/dist/ui/component-utilities/dateParsing.js +57 -0
  65. package/dist/ui/component-utilities/dropdownContext.ts +1 -0
  66. package/dist/ui/component-utilities/echarts.js +262 -0
  67. package/dist/ui/component-utilities/echartsThemes.js +453 -0
  68. package/dist/ui/component-utilities/formatTitle.js +24 -0
  69. package/dist/ui/component-utilities/formatting.js +258 -0
  70. package/dist/ui/component-utilities/getColumnExtents.js +79 -0
  71. package/dist/ui/component-utilities/getColumnSummary.js +67 -0
  72. package/dist/ui/component-utilities/getCompletedData.js +114 -0
  73. package/dist/ui/component-utilities/getDistinctCount.js +7 -0
  74. package/dist/ui/component-utilities/getDistinctValues.js +15 -0
  75. package/dist/ui/component-utilities/getSeriesConfig.js +236 -0
  76. package/dist/ui/component-utilities/getSortedData.js +7 -0
  77. package/dist/ui/component-utilities/getStackPercentages.js +43 -0
  78. package/dist/ui/component-utilities/getStackedData.js +17 -0
  79. package/dist/ui/component-utilities/getYAxisIndex.js +15 -0
  80. package/dist/ui/component-utilities/globalContexts.js +1 -0
  81. package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +119 -0
  82. package/dist/ui/component-utilities/inputUtils.ts +25 -0
  83. package/dist/ui/component-utilities/replaceNulls.js +14 -0
  84. package/dist/ui/component-utilities/tableUtils.ts +120 -0
  85. package/dist/ui/component-utilities/themeStores.ts +116 -0
  86. package/dist/ui/components/Area.svelte +174 -0
  87. package/dist/ui/components/AreaChart.svelte +150 -0
  88. package/dist/ui/components/Bar.svelte +326 -0
  89. package/dist/ui/components/BarChart.svelte +194 -0
  90. package/dist/ui/components/BigValue.svelte +69 -0
  91. package/dist/ui/components/Chart.svelte +1070 -0
  92. package/dist/ui/components/Column.svelte +172 -0
  93. package/dist/ui/components/DateRange.svelte +324 -0
  94. package/dist/ui/components/Dropdown.svelte +738 -0
  95. package/dist/ui/components/DropdownOption.svelte +21 -0
  96. package/dist/ui/components/ECharts.svelte +77 -0
  97. package/dist/ui/components/ErrorChart.svelte +54 -0
  98. package/dist/ui/components/GrapheneQuery.svelte +12 -0
  99. package/dist/ui/components/InlineDelta.svelte +150 -0
  100. package/dist/ui/components/Line.svelte +210 -0
  101. package/dist/ui/components/LineChart.svelte +178 -0
  102. package/dist/ui/components/PieChart.svelte +36 -0
  103. package/dist/ui/components/QueryLoad.svelte +82 -0
  104. package/dist/ui/components/Row.svelte +14 -0
  105. package/dist/ui/components/SortIcon.svelte +32 -0
  106. package/dist/ui/components/Table.svelte +19 -0
  107. package/dist/ui/components/TableCell.svelte +75 -0
  108. package/dist/ui/components/TableGroupRow.svelte +136 -0
  109. package/dist/ui/components/TableGroupToggle.svelte +42 -0
  110. package/dist/ui/components/TableHeader.svelte +242 -0
  111. package/dist/ui/components/TableRow.svelte +283 -0
  112. package/dist/ui/components/TableSubtotalRow.svelte +62 -0
  113. package/dist/ui/components/TableTotalRow.svelte +88 -0
  114. package/dist/ui/components/TextInput.svelte +92 -0
  115. package/dist/ui/components/_Table.svelte +516 -0
  116. package/dist/ui/internal/clientCache.ts +43 -0
  117. package/dist/ui/internal/queryEngine.ts +169 -0
  118. package/dist/ui/internal/telemetry.ts +28 -0
  119. package/dist/ui/internal/theme.ts +88 -0
  120. package/dist/ui/layout.svelte +3 -0
  121. package/dist/ui/playwright.config.ts +30 -0
  122. package/dist/ui/web.js +106 -0
  123. 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>