fluency-v8-components 1.5.9 → 1.6.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/dist/fluency-v8-components.es.js +51 -49
- package/dist/fluency-v8-components.umd.js +124 -120
- package/dist/{index-Bhs-DoPS.mjs → index-BAGLjhJi.mjs} +16923 -16496
- package/dist/index.css +1 -1
- package/dist/{index.es-C9xsQDxF.mjs → index.es-DW6Ls8hH.mjs} +1 -1
- package/package.json +1 -1
- package/src/assets/main.css +10 -6
- package/src/components/charts/RangeSlider.vue +513 -0
- package/src/components/charts/TimelineChart.vue +10 -2
- package/src/components/charts/TimelinePlot.vue +219 -0
- package/src/components/common/facet/Leaf.vue +23 -4
- package/src/components/index.js +2 -0
- package/src/components/page-structure/FacetPage.vue +2 -2
- package/src/constants/fpl2.js +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
package/src/assets/main.css
CHANGED
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
@utility std-light {
|
|
147
|
-
@apply bg-blue-
|
|
147
|
+
@apply bg-blue-100;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
@utility std-dark {
|
|
@@ -325,6 +325,10 @@
|
|
|
325
325
|
@apply w-10 h-10;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
@utility link {
|
|
329
|
+
@apply text-blue-400 hover:underline;
|
|
330
|
+
}
|
|
331
|
+
|
|
328
332
|
@font-face {
|
|
329
333
|
font-family: "Open Sans";
|
|
330
334
|
src: url("/OpenSans-VariableFont_wdth,wght.ttf");
|
|
@@ -510,22 +514,22 @@
|
|
|
510
514
|
}
|
|
511
515
|
|
|
512
516
|
.chip {
|
|
513
|
-
@apply
|
|
517
|
+
@apply inline-flex items-center rounded-md px-2 py-1 font-medium ring-1 ring-inset;
|
|
514
518
|
}
|
|
515
519
|
.select-chip {
|
|
516
520
|
@apply px-1 text-sm font-medium rounded-full bg-sky-200 dark:bg-sky-700;
|
|
517
521
|
}
|
|
518
522
|
.green-chip {
|
|
519
|
-
@apply bg-green-
|
|
523
|
+
@apply bg-green-400/10 ring-green-500/20 text-green-400;
|
|
520
524
|
}
|
|
521
525
|
.red-chip {
|
|
522
|
-
@apply bg-red-
|
|
526
|
+
@apply bg-red-400/10 ring-red-500/20 text-red-400;
|
|
523
527
|
}
|
|
524
528
|
.yellow-chip {
|
|
525
|
-
@apply bg-yellow-
|
|
529
|
+
@apply bg-yellow-400/10 ring-yellow-500/20 text-yellow-400;
|
|
526
530
|
}
|
|
527
531
|
.blue-chip {
|
|
528
|
-
@apply bg-blue-
|
|
532
|
+
@apply bg-blue-400/10 ring-blue-500/20 text-blue-400;
|
|
529
533
|
}
|
|
530
534
|
|
|
531
535
|
.image-position-box {
|
|
@@ -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: {
|