fluency-v8-components 1.5.9 → 1.6.0

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/index.css CHANGED
@@ -1 +1 @@
1
- .x-center[data-v-59ba8a5f]{transform:translate(-50%)}.left-tooltip[data-v-59ba8a5f]{transform:translate(-120%)}.right-tooltip[data-v-59ba8a5f]{transform:translate(20%)}.prism-editor-wrapper{width:100%;height:100%;display:flex;align-items:flex-start;overflow:auto;-o-tab-size:1.5em;tab-size:1.5em;-moz-tab-size:1.5em}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.prism-editor-wrapper .prism-editor__textarea{color:transparent!important}.prism-editor-wrapper .prism-editor__textarea::-moz-selection{background-color:#accef7!important;color:transparent!important}.prism-editor-wrapper .prism-editor__textarea::selection{background-color:#accef7!important;color:transparent!important}}.prism-editor-wrapper .prism-editor__container{position:relative;text-align:left;box-sizing:border-box;padding:0;overflow:hidden;width:100%}.prism-editor-wrapper .prism-editor__line-numbers{height:100%;overflow:hidden;flex-shrink:0;padding-top:4px;margin-top:0;margin-right:10px}.prism-editor-wrapper .prism-editor__line-number{text-align:right;white-space:nowrap}.prism-editor-wrapper .prism-editor__textarea{position:absolute;top:0;left:0;height:100%;width:100%;resize:none;color:inherit;overflow:hidden;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;-webkit-text-fill-color:transparent}.prism-editor-wrapper .prism-editor__editor,.prism-editor-wrapper .prism-editor__textarea{margin:0;border:0;background:none;box-sizing:inherit;display:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-variant-ligatures:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;-moz-tab-size:inherit;-o-tab-size:inherit;tab-size:inherit;text-indent:inherit;text-rendering:inherit;text-transform:inherit;white-space:pre-wrap;word-wrap:keep-all;overflow-wrap:break-word;padding:0}.prism-editor-wrapper .prism-editor__textarea--empty{-webkit-text-fill-color:inherit!important}.prism-editor-wrapper .prism-editor__editor{position:relative;pointer-events:none}pre[class*=language-],code[class*=language-]{color:#d4d4d4;font-size:13px;text-shadow:none;font-family:Menlo,Monaco,Consolas,Andale Mono,Ubuntu Mono,Courier New,monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]::selection,code[class*=language-]::selection,pre[class*=language-] *::selection,code[class*=language-] *::selection{text-shadow:none;background:#264f78}@media print{pre[class*=language-],code[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;background:#1e1e1e}:not(pre)>code[class*=language-]{padding:.1em .3em;border-radius:.3em;color:#db4c69;background:#1e1e1e}.namespace{opacity:.7}.token.doctype .token.doctype-tag{color:#569cd6}.token.doctype .token.name{color:#9cdcfe}.token.comment,.token.prolog,.token.timestamp{color:#6a9955}.token.errors{color:#c10015}.token.punctuation,.language-html .language-css,.language-html .language-javascript{color:#fdbe23}.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol,.token.inserted,.token.unit{color:#b5cea8}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.deleted{color:#ce9178}.language-css .token.string.url{text-decoration:underline}.token.operator,.token.entity{color:#d4d4d4}.token.operator.arrow{color:#569cd6}.token.atrule{color:#ce9178}.token.atrule .token.rule{color:#c586c0}.token.atrule .token.url{color:#9cdcfe}.token.atrule .token.url .token.function{color:#dcdcaa}.token.atrule .token.url .token.punctuation{color:#d4d4d4}.token.keyword.module,.token.keyword.control-flow{color:#c586c0}.token.function,.token.function .token.maybe-class-name,.token.brackets{color:#e9d299}.token.builtinfunction{color:#ce92d8}.token.keyword,.token.curlybraces{color:#569cd6}.token.regex{color:#d16969}.token.important{color:#569cd6}.token.italic{font-style:italic}.token.constant{color:#9cdcfe}.token.class-name,.token.maybe-class-name{color:#4ec9b0}.token.console,.token.parameter,.token.interpolation{color:#9cdcfe}.token.punctuation.interpolation-punctuation,.token.boolean{color:#569cd6}.token.property,.token.variable,.token.imports .token.maybe-class-name,.token.exports .token.maybe-class-name{color:#9cdcfe}.token.selector,.token.escape{color:#d7ba7d}.token.tag{color:#569cd6}.token.tag .token.punctuation,.token.cdata{color:gray}.token.attr-name{color:#9cdcfe}.token.attr-value,.token.attr-value .token.punctuation{color:#ce9178}.token.attr-value .token.punctuation.attr-equals{color:#d4d4d4}.token.entity{color:#569cd6}.token.namespace{color:#4ec9b0}pre[class*=language-javascript],code[class*=language-javascript],pre[class*=language-jsx],code[class*=language-jsx],pre[class*=language-typescript],code[class*=language-typescript],pre[class*=language-tsx],code[class*=language-tsx]{color:#9cdcfe}pre[class*=language-css],code[class*=language-css]{color:#ce9178}pre[class*=language-html],code[class*=language-html]{color:#d4d4d4}.language-regex .token.anchor{color:#dcdcaa}.language-html .token.punctuation{color:gray}pre[class*=language-]>code[class*=language-]{position:relative;z-index:1}.line-highlight.line-highlight{background:#f7ebc6;box-shadow:inset 5px 0 #f7d87c;z-index:0}.primary[data-v-2890730f]{font-weight:400;padding-top:5px}.primary[data-v-2890730f]:first-child{font-weight:400}
1
+ .x-center[data-v-59ba8a5f]{transform:translate(-50%)}.left-tooltip[data-v-59ba8a5f]{transform:translate(-120%)}.right-tooltip[data-v-59ba8a5f]{transform:translate(20%)}.prism-editor-wrapper{width:100%;height:100%;display:flex;align-items:flex-start;overflow:auto;-o-tab-size:1.5em;tab-size:1.5em;-moz-tab-size:1.5em}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.prism-editor-wrapper .prism-editor__textarea{color:transparent!important}.prism-editor-wrapper .prism-editor__textarea::-moz-selection{background-color:#accef7!important;color:transparent!important}.prism-editor-wrapper .prism-editor__textarea::selection{background-color:#accef7!important;color:transparent!important}}.prism-editor-wrapper .prism-editor__container{position:relative;text-align:left;box-sizing:border-box;padding:0;overflow:hidden;width:100%}.prism-editor-wrapper .prism-editor__line-numbers{height:100%;overflow:hidden;flex-shrink:0;padding-top:4px;margin-top:0;margin-right:10px}.prism-editor-wrapper .prism-editor__line-number{text-align:right;white-space:nowrap}.prism-editor-wrapper .prism-editor__textarea{position:absolute;top:0;left:0;height:100%;width:100%;resize:none;color:inherit;overflow:hidden;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;-webkit-text-fill-color:transparent}.prism-editor-wrapper .prism-editor__editor,.prism-editor-wrapper .prism-editor__textarea{margin:0;border:0;background:none;box-sizing:inherit;display:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-variant-ligatures:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;-moz-tab-size:inherit;-o-tab-size:inherit;tab-size:inherit;text-indent:inherit;text-rendering:inherit;text-transform:inherit;white-space:pre-wrap;word-wrap:keep-all;overflow-wrap:break-word;padding:0}.prism-editor-wrapper .prism-editor__textarea--empty{-webkit-text-fill-color:inherit!important}.prism-editor-wrapper .prism-editor__editor{position:relative;pointer-events:none}pre[class*=language-],code[class*=language-]{color:#d4d4d4;font-size:13px;text-shadow:none;font-family:Menlo,Monaco,Consolas,Andale Mono,Ubuntu Mono,Courier New,monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]::selection,code[class*=language-]::selection,pre[class*=language-] *::selection,code[class*=language-] *::selection{text-shadow:none;background:#264f78}@media print{pre[class*=language-],code[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;background:#1e1e1e}:not(pre)>code[class*=language-]{padding:.1em .3em;border-radius:.3em;color:#db4c69;background:#1e1e1e}.namespace{opacity:.7}.token.doctype .token.doctype-tag{color:#569cd6}.token.doctype .token.name{color:#9cdcfe}.token.comment,.token.prolog,.token.timestamp{color:#6a9955}.token.errors{color:#c10015}.token.punctuation,.language-html .language-css,.language-html .language-javascript{color:#fdbe23}.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol,.token.inserted,.token.unit{color:#b5cea8}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.deleted{color:#ce9178}.language-css .token.string.url{text-decoration:underline}.token.operator,.token.entity{color:#d4d4d4}.token.operator.arrow{color:#569cd6}.token.atrule{color:#ce9178}.token.atrule .token.rule{color:#c586c0}.token.atrule .token.url{color:#9cdcfe}.token.atrule .token.url .token.function{color:#dcdcaa}.token.atrule .token.url .token.punctuation{color:#d4d4d4}.token.keyword.module,.token.keyword.control-flow{color:#c586c0}.token.function,.token.function .token.maybe-class-name,.token.brackets{color:#e9d299}.token.builtinfunction{color:#ce92d8}.token.keyword,.token.curlybraces{color:#569cd6}.token.regex{color:#d16969}.token.important{color:#569cd6}.token.italic{font-style:italic}.token.constant{color:#9cdcfe}.token.class-name,.token.maybe-class-name{color:#4ec9b0}.token.console,.token.parameter,.token.interpolation{color:#9cdcfe}.token.punctuation.interpolation-punctuation,.token.boolean{color:#569cd6}.token.property,.token.variable,.token.imports .token.maybe-class-name,.token.exports .token.maybe-class-name{color:#9cdcfe}.token.selector,.token.escape{color:#d7ba7d}.token.tag{color:#569cd6}.token.tag .token.punctuation,.token.cdata{color:gray}.token.attr-name{color:#9cdcfe}.token.attr-value,.token.attr-value .token.punctuation{color:#ce9178}.token.attr-value .token.punctuation.attr-equals{color:#d4d4d4}.token.entity{color:#569cd6}.token.namespace{color:#4ec9b0}pre[class*=language-javascript],code[class*=language-javascript],pre[class*=language-jsx],code[class*=language-jsx],pre[class*=language-typescript],code[class*=language-typescript],pre[class*=language-tsx],code[class*=language-tsx]{color:#9cdcfe}pre[class*=language-css],code[class*=language-css]{color:#ce9178}pre[class*=language-html],code[class*=language-html]{color:#d4d4d4}.language-regex .token.anchor{color:#dcdcaa}.language-html .token.punctuation{color:gray}pre[class*=language-]>code[class*=language-]{position:relative;z-index:1}.line-highlight.line-highlight{background:#f7ebc6;box-shadow:inset 5px 0 #f7d87c;z-index:0}.primary[data-v-2890730f]{font-weight:400;padding-top:5px}.primary[data-v-2890730f]:first-child{font-weight:400}.rangeslider-container .axis path,.rangeslider-container .axis line{fill:none;stroke:#000;shape-rendering:crispEdges}.rangeslider-container .axis text{font:9px sans-serif}.rangeslider-container line.selected{stroke:#ff4800;stroke-width:4}.rangeslider-container .axis.path,.rangeslider-container .axis.line{fill:none;stroke:#000;shape-rendering:crispEdges}.rangeslider-container .axis.text{font:9px sans-serif}.rangeslider-container line.selected{stroke:#027be3;stroke-width:2}.rangeslider-container .brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.rangeslider-container .resize{display:inline!important;fill:#027be3;fill-opacity:1;stroke:gray;stroke-width:0px}.resize:focus-visible{outline:3px solid black}
@@ -1,4 +1,4 @@
1
- import { c as Da, _ as Va, g as il } from "./index-Bhs-DoPS.mjs";
1
+ import { c as Da, _ as Va, g as il } from "./index-CS1vHSYo.mjs";
2
2
  var fn = {}, cn = {}, cr, vn;
3
3
  function Q() {
4
4
  if (vn) return cr;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluency-v8-components",
3
- "version": "1.5.9",
3
+ "version": "1.6.0",
4
4
  "main": "dist/fluency-v8-components.umd.js",
5
5
  "module": "dist/fluency-v8-components.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,513 @@
1
+ <template>
2
+ <div
3
+ :id="chartID"
4
+ class="rangeslider-container"
5
+ style="max-height: 135px; max-width: 1185px;"
6
+ />
7
+ </template>
8
+
9
+ <script>
10
+ import { extent } from 'd3-array'
11
+ import { axisBottom } from 'd3-axis'
12
+ import { brushX } from 'd3-brush'
13
+ import { scaleLinear, scaleTime } from 'd3-scale'
14
+ import { pointer, select } from 'd3-selection'
15
+ import { timeHour } from 'd3-time'
16
+ import { timeFormat } from 'd3-time-format'
17
+
18
+ const ONE_HOUR_MS = 60 * 60 * 1000;
19
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
20
+
21
+ export default {
22
+ props: {
23
+ chartData: {
24
+ type: Array,
25
+ default: () => []
26
+ },
27
+ chartID: {
28
+ type: String,
29
+ default: 'range-slider'
30
+ },
31
+ from: {
32
+ type: Number,
33
+ required: true
34
+ },
35
+ to: {
36
+ type: Number,
37
+ required: true
38
+ },
39
+ theme: {
40
+ type: String,
41
+ default: "light"
42
+ }
43
+ },
44
+ data () {
45
+ return {
46
+ currentTS: new Date(),
47
+ handleWidth: 10,
48
+ interval: 4,
49
+ margin: { top: 10, right: 15, bottom: 45, left: 15 },
50
+ maxTo: null,
51
+ minFrom: null,
52
+ previousSelectionValue: null,
53
+ startTimeIsDifferent: false,
54
+ endTimeIsDifferent: false,
55
+ size: { height: 100, width: 1000 },
56
+ week: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
57
+ xScaleTime: null,
58
+ knownFrom: this.from,
59
+ knownTo: this.to
60
+ }
61
+ },
62
+ computed: {
63
+ start () {
64
+ if (this.from < this.minFrom) {
65
+ return this.minFrom
66
+ } else {
67
+ return this.from
68
+ }
69
+ },
70
+ end () {
71
+ if (this.to > this.maxTo) {
72
+ return this.maxTo
73
+ } else {
74
+ return this.to
75
+ }
76
+ },
77
+ isDark () {
78
+ return this.theme === "dark"
79
+ },
80
+ },
81
+ watch: {
82
+ chartData () {
83
+ const canvas = select(`#${this.chartID}`)
84
+ canvas.select('svg').remove()
85
+ this.drawChart()
86
+ },
87
+ from () {
88
+ this.updateSlider()
89
+ },
90
+ to () {
91
+ this.updateSlider()
92
+ },
93
+ theme () {
94
+ const canvas = select(`#${this.chartID}`)
95
+ canvas.select('svg').remove()
96
+ this.drawChart()
97
+ },
98
+ },
99
+ mounted () {
100
+ this.drawChart()
101
+ },
102
+ methods: {
103
+ determineTickTimeIntervalHours (timeDomain) {
104
+ const days = Math.round((timeDomain[1] - timeDomain[0]) / ONE_DAY_MS)
105
+
106
+ let interval = 4
107
+
108
+ if (days > 6 && days <= 12) {
109
+ interval = 8
110
+ } else if (days > 12 && days <= 20) {
111
+ interval = 12
112
+ } else if (days > 20) {
113
+ interval = 24
114
+ }
115
+
116
+ return interval
117
+ },
118
+ roundTimeMillisecends (interval, time) {
119
+ return this.roundTime(interval, new Date(time))
120
+ },
121
+ roundTimeDate (interval, date) {
122
+ return new Date(this.roundTime(interval, date))
123
+ },
124
+ roundTime (interval, date) {
125
+ const newDate = new Date(date)
126
+ const h =
127
+ newDate.getHours() +
128
+ newDate.getMinutes() / 60 +
129
+ newDate.getSeconds() / 3600 +
130
+ newDate.getMilliseconds() / 3600000
131
+
132
+ newDate.setHours(Math.round(h / interval) * interval)
133
+ newDate.setMinutes(0)
134
+ newDate.setSeconds(0)
135
+ newDate.setMilliseconds(0)
136
+
137
+ return newDate.getTime()
138
+ },
139
+ drawChart () {
140
+ const canvas = select(`#${this.chartID}`)
141
+
142
+ let textColor = 'black'
143
+ let bgColor = 'white'
144
+ if (this.isDark) {
145
+ textColor = 'white'
146
+ bgColor = '#303030'
147
+ }
148
+
149
+ console.log('textColor', textColor)
150
+
151
+ const svg = canvas
152
+ .append('svg')
153
+ .attr('preserveAspectRatio', 'xMinYMin meet')
154
+
155
+ .attr('viewBox', `0 0 ${this.size.width} ${this.size.height + 10}`)
156
+ .append('g')
157
+ .attr('background-color', bgColor)
158
+ .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')')
159
+
160
+ const height = this.size.height - this.margin.top - this.margin.bottom
161
+ const width = this.size.width - this.margin.left - this.margin.right
162
+ const flowLine = this.chartData.map((d, i, arr) => {
163
+ const next = arr[i + 1]
164
+ const prev = arr[i - 1]
165
+
166
+ return {
167
+ x: d[0],
168
+ y: d[1],
169
+ x1: d[0],
170
+ y1: d[1],
171
+ x2: next ? next[0] : prev[0],
172
+ y2: next ? next[1] : prev[1]
173
+ }
174
+ })
175
+
176
+ const timeSeries = this.chartData.map(d => d[0])
177
+ const maxDate = timeSeries[timeSeries.length - 1]
178
+
179
+ // add an extra hour to the end since the interval is 1 hour
180
+ // this allows a full selection to the current time
181
+ if (Date.now() - maxDate <= ONE_HOUR_MS) {
182
+ const oneHourAhead = maxDate + ONE_HOUR_MS
183
+ timeSeries.push(oneHourAhead)
184
+ }
185
+
186
+ // scales the time range to pixels
187
+ const xScaleTime = scaleTime()
188
+ .domain(extent(timeSeries))
189
+ .range([0, width])
190
+ this.xScaleTime = xScaleTime
191
+
192
+ const interval = this.determineTickTimeIntervalHours(xScaleTime.domain())
193
+ this.interval = interval
194
+
195
+ this.maxTo = timeSeries[timeSeries.length - 1]
196
+ this.minFrom = timeSeries[0]
197
+ this.currentTS = this.maxTo
198
+
199
+ const timeFormatted = timeFormat('%H')
200
+
201
+ const xAxis = axisBottom(xScaleTime)
202
+ .ticks(timeFormatted.hours, interval)
203
+ .tickFormat(d => {
204
+ const hours = d.getHours()
205
+
206
+ if (hours === 0) {
207
+ return d.toLocaleString('en-us', { month: 'short' }) + ' ' + d.getDate() + ' ' + this.week[d.getDay()]
208
+ } else {
209
+ if (hours >= 12) {
210
+ return hours + ' pm'
211
+ } else {
212
+ return hours < 10 ? '0' + hours + ' am' : hours + ' am'
213
+ }
214
+ }
215
+ })
216
+
217
+ // yScale for upper histogram chart
218
+ const yScaleLinear = scaleLinear()
219
+ .domain(extent(this.chartData, d => d[1]))
220
+ .range([height - 2, 0])
221
+ this.yScaleLinear = yScaleLinear
222
+
223
+ // draws the x-axis in the middle with black/red text labeling
224
+ svg
225
+ .append('g')
226
+ .attr('class', 'xAxis')
227
+ .attr('transform', 'translate(0,' + height + ')')
228
+ .call(xAxis)
229
+ .selectAll('text')
230
+ .style('text-anchor', 'end')
231
+ .attr('dx', '-.8em')
232
+ .attr('dy', '.15em')
233
+ .attr('transform', 'rotate(-35)')
234
+ .style('fill', d => (d.getHours() === 0 ? '#027be3' : textColor))
235
+
236
+ // draws the histogram lines
237
+ const lines = svg
238
+ .append('g')
239
+ .selectAll('line')
240
+ .data(flowLine)
241
+ .enter()
242
+ .append('line')
243
+ .attr('class', 'flow-line')
244
+ .attr('x1', d => this.xScaleTime(d.x1))
245
+ .attr('y1', d => this.yScaleLinear(d.y1))
246
+ .attr('x2', d => this.xScaleTime(d.x2))
247
+ .attr('y2', d => this.yScaleLinear(d.y2))
248
+ .attr('stroke', '#9e9e9e')
249
+ .attr('stroke-width', '1')
250
+
251
+ // create brush handle (blue vertical bars)
252
+ const brushResizePath = (_, i) => {
253
+ return (
254
+ 'M0,0h' +
255
+ (i <= 0 ? '-' : '') +
256
+ this.handleWidth +
257
+ 'v' +
258
+ (height + 10) +
259
+ 'h' +
260
+ (i <= 0 ? '' : '-') +
261
+ this.handleWidth +
262
+ 'z'
263
+ )
264
+ }
265
+
266
+ const brush = brushX()
267
+ .extent([[0, -9], [width, height - 1]])
268
+ .on('start brush', event => brushMoved(event, this))
269
+ .on('end', event => brushEnd(event, this))
270
+ this.brush = brush
271
+
272
+ // create a brush for the draggable range selection / sliding window
273
+ const gBrush = svg
274
+ .append('g')
275
+ .attr('class', 'brush')
276
+ .call(brush)
277
+ .call(g =>
278
+ g
279
+ .select('.overlay')
280
+ .datum({ type: 'selection' })
281
+ .on('mousedown touchstart', beforeBrushStarted)
282
+ )
283
+ this.gBrush = gBrush
284
+
285
+ // adds the blue bars to each end of the slider
286
+ const handle = gBrush
287
+ .selectAll('.handle--custom')
288
+ .data([{ type: 'w' }, { type: 'e' }])
289
+ .enter()
290
+ .append('path')
291
+ .attr('id', (_, index) => index === 0 ? 'handle-left' : 'handle-right')
292
+ .attr('class', 'resize')
293
+ .attr('cursor', 'ew-resize')
294
+ .attr('d', brushResizePath)
295
+ .attr('focusable', 'true')
296
+ .attr('tabindex', 0)
297
+ .attr('role', 'slider')
298
+ .attr('aria-label', (_, index) => index === 0 ? 'from date/time value' : 'to date/time value')
299
+ .on('keydown', (event) => {
300
+ let startTime = new Date(this.from)
301
+ let endTime = new Date(this.to)
302
+
303
+ console.log('keydown event', event)
304
+
305
+ let STEP
306
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
307
+ STEP = -1
308
+ } else if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
309
+ STEP = 1
310
+ } else if (event.key === 'PageUp') {
311
+ STEP = 10
312
+ } else if (event.key === 'PageDown') {
313
+ STEP = -10
314
+ }
315
+
316
+ const isLeftHandle = document.activeElement.id === 'handle-left'
317
+ if (isLeftHandle) {
318
+ startTime = timeHour.offset(startTime, STEP)
319
+ } else {
320
+ endTime = timeHour.offset(endTime, STEP)
321
+ }
322
+
323
+ // only allow updates if the time delta is an hour or more
324
+ const deltaSeconds = (endTime - startTime) / 1000
325
+ if (deltaSeconds > 60 * 60 * Math.abs(STEP)) {
326
+ this.$emit('sliderChanged', { from: startTime, to: endTime })
327
+ }
328
+ })
329
+
330
+ // highlight area between startTime and endTime
331
+ gBrush.call(brush.move, [xScaleTime(this.start), xScaleTime(this.end)])
332
+
333
+ // if chart is clicked outside of selection re-center the selection
334
+ function beforeBrushStarted (event) {
335
+ const dx = xScaleTime(1) - xScaleTime(0)
336
+ const [cx] = pointer(event, this)
337
+
338
+ let [x0, x1] = [cx - dx / 2, cx + dx / 2]
339
+ const [X0, X1] = xScaleTime.range()
340
+
341
+ while (Math.abs(x0 - x1) < 60) {
342
+ if (x0 > X0 && x0 - 1 > X0) {
343
+ x0 -= 1
344
+ } else if (x0 > X0) {
345
+ x0 = X0
346
+ }
347
+
348
+ if (x1 < X1 && x1 + 1 < X1) {
349
+ x1 += 1
350
+ } else if (x1 < X1) {
351
+ x1 = X1
352
+ }
353
+ }
354
+ select(this.parentNode).call(brush.move, x1 > X1 ? [X1 - dx, X1] : x0 < X0 ? [X0, X0 + dx] : [x0, x1])
355
+ }
356
+
357
+ function brushMoved (selection, oldState) {
358
+ // keep blue
359
+ // brush handles aligned with selection ends
360
+
361
+ if (isNaN(selection.selection[0]) || isNaN(selection.selection[1])) {
362
+ selection.selection[0] = oldState.knownFrom
363
+ selection.selection[1] = oldState.knownTo
364
+ } else {
365
+ oldState.knownFrom = selection.selection[0]
366
+ oldState.knownTo = selection.selection[1]
367
+ }
368
+
369
+ handle.attr('display', null).attr('transform', function (_, i) {
370
+ return 'translate(' + [selection.selection[i], -height / 5] + ')'
371
+ })
372
+
373
+ handle.attr('aria-valuenow', (_, index) => index === 0 ? oldState.from / 1000 : oldState.to / 1000) // should be unixEpochTime
374
+ handle.attr('aria-valuemin', oldState.minFrom / 1000) // unixEpochTime
375
+ handle.attr('aria-valuemax', oldState.maxTo / 1000) // unixEpochTime
376
+ handle.attr('aria-valuetext', (_, index) => {
377
+ const formatTime = timeFormat('%Y/%m/%d %H:%M')
378
+ return index === 0 ? formatTime(new Date(oldState.from)) : formatTime(new Date(oldState.to))
379
+ })
380
+
381
+ // changes the line color to red when selected ... looking for true false
382
+
383
+ // lines.classed('selected', false)
384
+
385
+ lines.classed('selected', (data, index, localLines) => {
386
+ const start = selection.selection[0]
387
+ const end = selection.selection[1]
388
+ const line = select(localLines[index])
389
+ const x1 = line.attr('x1'),
390
+ x2 = line.attr('x2')
391
+ return x1 && x1 >= start && x2 <= end
392
+ })
393
+
394
+ // only emit when the slider is moved and not for incoming updates
395
+ if (selection.sourceEvent) {
396
+ const brushEnds = selection.selection.map(oldState.xScaleTime.invert)
397
+ oldState.emitSliderChange(brushEnds)
398
+ }
399
+ }
400
+
401
+ // end event handler
402
+ function brushEnd (event, oldState) {
403
+ if (!event.sourceEvent || !event.selection) {
404
+ return
405
+ }
406
+
407
+ const brushEnds = event.selection.map(oldState.xScaleTime.invert)
408
+ oldState.emitSliderChange(brushEnds)
409
+ }
410
+ },
411
+
412
+ emitSliderChange (brushEnds) {
413
+ let startTime = brushEnds[0]
414
+ let endTime = brushEnds[1]
415
+
416
+ // round the start/end selection time only if it was moved
417
+ if (this.startTimeIsDifferent) {
418
+ startTime = this.roundTimeDate(1, startTime)
419
+ }
420
+
421
+ if (this.endTimeIsDifferent) {
422
+ endTime = this.roundTimeDate(1, endTime)
423
+ }
424
+
425
+ // reach the left border
426
+ if (endTime.getTime() >= this.maxTo) {
427
+ endTime = new Date(this.currentTS)
428
+
429
+ // make sure currentTS is not out of display bound
430
+ if (this.currentTS >= this.maxTo + this.interval * 3600000) {
431
+ endTime = new Date(this.maxTo)
432
+ }
433
+ }
434
+
435
+ // emit selection changed event
436
+ if (this.timeout) {
437
+ clearTimeout(this.timeout)
438
+ }
439
+
440
+ // debounce for performance
441
+ this.timeout = setTimeout(() => {
442
+ this.$emit('sliderChanged', { from: startTime, to: endTime })
443
+ }, 10)
444
+ },
445
+ updateSlider () {
446
+ this.gBrush.call(this.brush.move, [this.xScaleTime(this.start), this.xScaleTime(this.end)])
447
+ }
448
+ }
449
+ }
450
+ </script>
451
+
452
+ <style>
453
+ .rangeslider-container {
454
+ .axis path, .axis line {
455
+ fill: none;
456
+ stroke: #000;
457
+ shape-rendering: crispEdges;
458
+ }
459
+
460
+ .axis text {
461
+ font: 9px sans-serif;
462
+ }
463
+
464
+ line.selected {
465
+ stroke:#ff4800;
466
+ stroke-width: 4;
467
+ }
468
+
469
+ .brush .extent {
470
+ fill-opacity: .125;
471
+ shape-rendering: crispEdges;
472
+ }
473
+
474
+ .resize {
475
+ display: inline !important; /* show when empty */
476
+ fill: #027be3;
477
+ fill-opacity: 1.0;
478
+ stroke: #808080;
479
+ stroke-width: 0px;
480
+ }
481
+ }
482
+
483
+ .rangeslider-container .axis.path{
484
+ fill: none;stroke: #000;
485
+ shape-rendering: crispEdges;
486
+ }
487
+ .rangeslider-container .axis.line{
488
+ fill: none;stroke: #000;
489
+ shape-rendering: crispEdges;
490
+ }
491
+ .rangeslider-container .axis.text{
492
+ font: 9px sans-serif;
493
+ }
494
+ .rangeslider-container line.selected{
495
+ stroke:#027be3;
496
+ stroke-width: 2;
497
+ }
498
+ .rangeslider-container .brush .extent {
499
+ fill-opacity: .125;
500
+ shape-rendering: crispEdges;
501
+ }
502
+ .rangeslider-container .resize{
503
+ display: inline !important; /* show when empty */
504
+ fill: #027be3;
505
+ fill-opacity: 1.0;
506
+ stroke: #808080;
507
+ stroke-width: 0px;
508
+ }
509
+
510
+ .resize:focus-visible {
511
+ outline: 3px solid black;
512
+ }
513
+ </style>
@@ -27,6 +27,14 @@ const props = defineProps({
27
27
  type: Boolean,
28
28
  default: false,
29
29
  },
30
+ width: {
31
+ type: Number,
32
+ default: 1200,
33
+ },
34
+ height: {
35
+ type: Number,
36
+ default: 300,
37
+ },
30
38
  theme: {
31
39
  type: String,
32
40
  default: "light",
@@ -201,8 +209,8 @@ function plotGraph() {
201
209
  const plot = Plot.plot({
202
210
  marginLeft,
203
211
  marginRight,
204
- width: 1200,
205
- height: 320,
212
+ width: props.width,
213
+ height: props.height,
206
214
  marginBottom: 50,
207
215
  inset: 20,
208
216
  style: {
@@ -9,9 +9,11 @@
9
9
  {{ parent.title }}
10
10
  <span class="font-light">({{ children.length }})</span>
11
11
  </div>
12
- <div class="flex">
12
+ <div class="flex pr-1">
13
13
  <PlusCircleIcon class="icon-lg" :class="noMore()" @click="topUp()" />
14
14
  <MinusCircleIcon class="icon-lg" :class="noLess()" @click="topDown()" />
15
+ <ArrowDownCircleIcon v-if="descending" class="icon-lg std-blue-text cursor-pointer" @click="sortData" />
16
+ <ArrowUpCircleIcon v-else class="icon-lg std-blue-text cursor-pointer" @click="sortData" />
15
17
  </div>
16
18
  <Tooltip
17
19
  v-if="props.tooltip"
@@ -22,7 +24,7 @@
22
24
  />
23
25
  </div>
24
26
  <div
25
- v-for="child in children.slice(0, topLevel)"
27
+ v-for="child in filtered"
26
28
  class="flex justify-between py-1"
27
29
  >
28
30
  <div class="flex items-center min-w-0">
@@ -40,11 +42,12 @@
40
42
  </template>
41
43
 
42
44
  <script lang="ts" setup>
43
- import { ref } from "vue";
44
- import { PlusCircleIcon, MinusCircleIcon } from "@heroicons/vue/20/solid";
45
+ import { ref, computed } from "vue";
46
+ import { PlusCircleIcon, MinusCircleIcon, ArrowUpCircleIcon, ArrowDownCircleIcon } from "@heroicons/vue/20/solid";
45
47
  import Tooltip from "../Tooltip.vue";
46
48
  import TriState from "./TriState.vue";
47
49
 
50
+ // props and emits
48
51
  const props = defineProps({
49
52
  parent: {
50
53
  type: Object,
@@ -62,9 +65,21 @@ const props = defineProps({
62
65
  },
63
66
  });
64
67
  const emits = defineEmits(["newState"]);
68
+ // states
65
69
  const topLevel = ref(props.parent.show);
66
70
  const tooltip = ref(false);
71
+ const descending = ref(true);
72
+ // computed
73
+ const filtered = computed(() => {
74
+ const copy = JSON.parse(JSON.stringify(props.children))
75
+ if (!descending.value) {
76
+ copy.reverse();
77
+ }
78
+ return copy.slice(0, topLevel.value);
79
+ });
80
+
67
81
 
82
+ // function defs
68
83
  function noMore() {
69
84
  if (topLevel.value < props.children.length) {
70
85
  return "std-blue-text cursor-pointer";
@@ -91,6 +106,10 @@ function topDown() {
91
106
  }
92
107
  }
93
108
 
109
+ function sortData() {
110
+ descending.value = !descending.value
111
+ }
112
+
94
113
  function updateParent(parent: string, child: string, newState: string) {
95
114
  emits("newState", parent, child, newState);
96
115
  }
@@ -119,6 +119,7 @@ export { default as StackedChartClustered } from "./charts/StackedChartClustered
119
119
  export { default as PieChart } from "./charts/PieChart.vue";
120
120
  export { default as ProgressChart } from "./charts/ProgressChart.vue";
121
121
  export { default as TimelineChart } from "./charts/TimelineChart.vue";
122
+ export { default as RangeSlider } from "./charts/RangeSlider.vue"
122
123
  export { default as EmptyChart } from "./charts/EmptyChart.vue";
123
124
  export { default as WorkflowChart } from "./charts/WorkflowChart.vue";
124
125
 
@@ -20,7 +20,7 @@
20
20
  <div class="grid grid-cols-3 md:grid-cols-4">
21
21
  <div
22
22
  :class="[
23
- 'w-full col-span-3 md:col md:col-span-1 md:overflow-auto',
23
+ 'w-full col-span-3 md:col md:col-span-1 md:overflow-auto custom-scrollbar',
24
24
  { 'md:max-h-[calc(100vh-255px)]': searchBar },
25
25
  { 'md:max-h-[calc(100vh-200px)]': !searchBar },
26
26
  ]"