Skip to content

Commit 8344f0e

Browse files
committed
Chart UI
1 parent 169c9e7 commit 8344f0e

File tree

1 file changed

+181
-51
lines changed

1 file changed

+181
-51
lines changed

src/components/TimeIntervalChart.vue

Lines changed: 181 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<template>
2-
<Bar :data="data" :options="options" v-if="isLoaded" />
2+
<p class="description">{{ t('intervalChartChart.description') }}</p>
3+
<div ref="chart" id="timeIntervalChartD3"></div>
34
</template>
45

56
<script lang="ts">
@@ -9,22 +10,175 @@ export default {
910
</script>
1011

1112
<script lang="ts" setup>
12-
import { Bar } from 'vue-chartjs';
13-
import {
14-
Chart as ChartJS,
15-
Title,
16-
Tooltip,
17-
Legend,
18-
BarElement,
19-
LinearScale,
20-
CategoryScale,
21-
} from 'chart.js';
2213
import { onMounted, ref } from 'vue';
2314
import { injecStorage } from '../storage/inject-storage';
2415
import { StorageDeserializeParam } from '../storage/storage-params';
2516
import { TimeInterval } from '../entity/time-interval';
2617
import { todayLocalDate } from '../utils/date';
18+
import { useI18n } from 'vue-i18n';
2719
import { convertHoursToTime, convertStringTimeIntervalToSeconds } from '../utils/converter';
20+
import * as d3 from 'd3';
21+
22+
const { t } = useI18n();
23+
24+
const chart = ref<any>();
25+
26+
onMounted(async () => {
27+
const timeIntervalList = (await storage.getDeserializeList(
28+
StorageDeserializeParam.TIMEINTERVAL_LIST,
29+
)) as TimeInterval[];
30+
31+
// data.intervals.forEach(function (interval) {
32+
// resultArr.push({ domain: data.domain, interval: interval });
33+
// });
34+
const data = [
35+
{
36+
domain: 'google.com',
37+
interval: '10:12:18-10:25:17',
38+
},
39+
{
40+
domain: 'habr.com',
41+
interval: '10:28:18-10:31:17',
42+
},
43+
{
44+
domain: 'medium.com',
45+
interval: '11:41:18-11:48:17',
46+
},
47+
{
48+
domain: 'xy.com',
49+
interval: '02:41:18-03:01:17',
50+
},
51+
];
52+
drawIntervalChart(data);
53+
});
54+
55+
function drawIntervalChart(data) {
56+
data.forEach(function (item) {
57+
var hFrom = getHourFrom(item.interval);
58+
var hTo = getHourTo(item.interval);
59+
if (hFrom != hTo) {
60+
var sourceTimeFrom = item.interval.split('-')[0].split(':');
61+
var sourceTimeTo = item.interval.split('-')[1].split(':');
62+
var timeTo = `${sourceTimeFrom[0]}:59:59`;
63+
var timeFrom = `${sourceTimeTo[0]}:00:00`;
64+
data.push({ domain: item.domain, interval: item.interval.split('-')[0] + '-' + timeTo });
65+
data.push({ domain: item.domain, interval: timeFrom + '-' + item.interval.split('-')[1] });
66+
}
67+
});
68+
69+
const margin = { top: 10, right: 10, bottom: 20, left: 20 };
70+
const width = chart.value.offsetWidth;
71+
const height = 400;
72+
73+
const tickDistance = 4.38;
74+
75+
const tooltip = d3
76+
.select('#timeIntervalChartD3')
77+
.append('div')
78+
.style('opacity', 0)
79+
.style('display', 'none')
80+
.style('position', 'absolute')
81+
.style('background-color', 'white')
82+
.style('color', 'black')
83+
.style('border', '1px solid grey')
84+
.attr('class', 'tooltip')
85+
.style('border-width', '1px')
86+
.style('border-radius', '3px')
87+
.style('padding', '5px');
88+
89+
const mouseover = function (e: any) {
90+
tooltip.style('opacity', 1).style('display', 'block');
91+
d3.select('.tooltip')
92+
.style('left', e.layerX + 15 + 'px')
93+
.style('top', e.layerY + 15 + 'px');
94+
};
95+
const mousemove = function (event: any, data: any) {
96+
tooltip.html(`<b>${data.domain}</b><br>${data.interval}`);
97+
};
98+
const mouseleave = function (e: any) {
99+
tooltip.style('opacity', 0).style('display', 'none');
100+
};
101+
102+
//create the svg
103+
const svg = d3
104+
.select('#timeIntervalChartD3')
105+
.append('svg')
106+
.attr('width', width + margin.left + margin.right)
107+
.attr('height', height + margin.top + margin.bottom)
108+
.append('g')
109+
.attr('transform', `translate(${margin.left},${margin.top})`);
110+
111+
const y = d3.scaleLinear([height, 0]).domain([0, 60]);
112+
const yAxis = d3.axisLeft(y);
113+
114+
const x = d3.scaleLinear([0, width]).domain([0, 24]);
115+
const xAxis = d3.axisBottom(x).ticks(24);
116+
117+
svg
118+
.append('g')
119+
.attr('class', 'grid')
120+
.style('color', '#e5e5e5')
121+
.attr('transform', `translate(0, ${height})`)
122+
.call(xAxis.tickSize(-height));
123+
124+
svg.append('g').attr('class', 'grid').style('color', '#e5e5e5').call(yAxis.tickSize(-width));
125+
svg.selectAll('text').style('fill', 'black');
126+
127+
//draw the bars, offset y and bar height based on data
128+
svg
129+
.selectAll('.bar')
130+
.data(data)
131+
.enter()
132+
.append('rect')
133+
.style('fill', '#5668e2')
134+
.style('cursor', 'pointer')
135+
.style('stroke-width', '1')
136+
.attr('class', 'bar')
137+
.attr('x', (data: any) => x(getHourFrom(data.interval)) + 2)
138+
.attr('width', 25)
139+
.attr('y', (data: any) => y(getMinutesTo(data.interval)) - 1)
140+
.attr('height', (data: any) => {
141+
const offset = getMinutesTo(data.interval) - getMinutesFrom(data.interval);
142+
if (offset == 0) {
143+
const offsetSeconds = getSecondsTo(data.interval) - getSecondsFrom(data.interval);
144+
if (offsetSeconds <= 3) return 0;
145+
else return 1;
146+
} else return offset * tickDistance;
147+
})
148+
.on('mouseover', mouseover)
149+
.on('mousemove', mousemove)
150+
.on('mouseleave', mouseleave);
151+
152+
function getHourFrom(interval: string): number {
153+
const time = interval.split('-')[0];
154+
return Number(time.split(':')[0]);
155+
}
156+
157+
function getHourTo(interval: string): number {
158+
var time = interval.split('-')[1];
159+
return Number(time.split(':')[0]);
160+
}
161+
162+
function getMinutesFrom(interval: string): number {
163+
var time = interval.split('-')[0];
164+
return Number(time.split(':')[1]);
165+
}
166+
167+
function getMinutesTo(interval: string): number {
168+
var time = interval.split('-')[1];
169+
return Number(time.split(':')[1]);
170+
}
171+
172+
function getSecondsFrom(interval: string): number {
173+
var time = interval.split('-')[0];
174+
return Number(time.split(':')[2]);
175+
}
176+
177+
function getSecondsTo(interval: string): number {
178+
var time = interval.split('-')[1];
179+
return Number(time.split(':')[2]);
180+
}
181+
}
28182
29183
type DataForChart = {
30184
summary: number;
@@ -40,8 +194,6 @@ type DomainsInterval = {
40194
41195
const storage = injecStorage();
42196
43-
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
44-
45197
const options = ref<any>();
46198
const data = ref<any>();
47199
const isLoaded = ref<boolean>();
@@ -164,46 +316,24 @@ async function buildChart() {
164316
minutes.push(index);
165317
}
166318
167-
const dataForChart = fillData(timeIntervalList);
168-
data.value = {
169-
labels: hours,
170-
datasets: dataForChart,
171-
};
172-
173-
options.value = {
174-
responsive: true,
175-
maintainAspectRatio: false,
176-
plugins: {
177-
legend: {
178-
position: 'none',
179-
},
180-
tooltip: {
181-
callbacks: {
182-
label: function (context: any) {
183-
return `${context.label}:00-${Number(context.label) + 1}:00 ${convertHoursToTime(
184-
context.raw,
185-
)}`;
186-
},
187-
},
188-
},
189-
},
190-
scales: {
191-
y: {
192-
min: 0,
193-
max: 60,
194-
ticks: {
195-
stepSize: 5,
196-
},
197-
},
198-
x: {
199-
stacked: true,
200-
min: 0,
201-
max: 23,
202-
},
203-
},
204-
};
205319
isLoaded.value = true;
206320
}
207321
208322
onMounted(async () => await buildChart());
209323
</script>
324+
325+
<style scoped>
326+
.block {
327+
display: inline-block;
328+
border: 1px rgb(197, 197, 197) solid;
329+
background-color: white;
330+
height: 40px;
331+
width: 40px;
332+
}
333+
334+
.grid line {
335+
stroke: gray;
336+
stroke-opacity: 0.2;
337+
color: black;
338+
}
339+
</style>

0 commit comments

Comments
 (0)