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/fluency-v8-components.es.js +46 -45
- package/dist/fluency-v8-components.umd.js +113 -113
- package/dist/{index-Bhs-DoPS.mjs → index-CS1vHSYo.mjs} +15550 -15323
- package/dist/index.css +1 -1
- package/dist/{index.es-C9xsQDxF.mjs → index.es-I_IDoGTc.mjs} +1 -1
- package/package.json +1 -1
- package/src/components/charts/RangeSlider.vue +513 -0
- package/src/components/charts/TimelineChart.vue +10 -2
- package/src/components/common/facet/Leaf.vue +23 -4
- package/src/components/index.js +1 -0
- package/src/components/page-structure/FacetPage.vue +1 -1
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}
|
package/package.json
CHANGED
|
@@ -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:
|
|
205
|
-
height:
|
|
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
|
|
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
|
}
|
package/src/components/index.js
CHANGED
|
@@ -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
|
]"
|