Discover: SidebarFilter Timeframes div & slider done.

This commit is contained in:
chiayin
2023-06-01 14:54:39 +08:00
parent 9927d34099
commit 90a5b7532a
3 changed files with 20 additions and 534 deletions

View File

@@ -6,9 +6,7 @@
<span class="material-symbols-outlined mr-2 text-base">info</span> <span class="material-symbols-outlined mr-2 text-base">info</span>
<p>Select or fill in a time range.</p> <p>Select or fill in a time range.</p>
</div> </div>
<!-- <Chart type="line" :data="chartData" :options="chartOptions" class="h-3/5" ref="TimeChart" /> -->
<div class="chartContainer"> <div class="chartContainer">
<!-- <Chart type="line" :data="chartData" :options="chartOptions" class="h-3/5" ref="TimeChart" /> -->
<canvas id="chartCanvasId"></canvas> <canvas id="chartCanvasId"></canvas>
<div id="chart-mask-left"></div> <div id="chart-mask-left"></div>
<div id="chart-mask-right"></div> <div id="chart-mask-right"></div>
@@ -16,10 +14,9 @@
<div class="px-2"> <div class="px-2">
<!-- <Slider v-model="selectArea" :step="1" :min="1" :max="timeFrameTotal" range class="mx-2"/> --> <!-- <Slider v-model="selectArea" :step="1" :min="1" :max="timeFrameTotal" range class="mx-2"/> -->
<Slider v-model="selectArea" :step="1" :min="0" :max="100" range class="mx-2" @change="changeSelectArea($event)"/> <Slider v-model="selectArea" :step="1" :min="0" :max="selectRange" range class="mx-2" @change="changeSelectArea($event)"/>
<br/> <br/>
{{ selectArea }}<br/> {{ selectArea }}<br/>
total: {{ timeFrameData.length }}<br/>
</div> </div>
<div> <div>
<div @click.stop.prevent=""> <div @click.stop.prevent="">
@@ -34,11 +31,8 @@
<script> <script>
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import AllMapDataStore from '@/stores/allMapData.js'; import AllMapDataStore from '@/stores/allMapData.js';
import getMoment from 'moment';
import { timeRange, yTimeRange } from '@/module/timeframes.js';
import { Chart, registerables } from 'chart.js'; import { Chart, registerables } from 'chart.js';
import 'chartjs-adapter-date-fns'; import 'chartjs-adapter-date-fns';
import "chart.js/auto";
export default{ export default{
setup() { setup() {
@@ -49,16 +43,15 @@ export default{
}, },
data() { data() {
return { return {
// timeFrameData: [], selectRange: 300, // 更改 select 的切分數
chartOptions: null, selectArea: [1,10],
selectArea: [1, 10],
date: null, date: null,
chart: null, chart: null,
canvasId: null, canvasId: null,
} }
}, },
computed: { computed: {
// Set Slider Time Range滑塊範圍: 最小值 + data(10 個) + 最大 // 加入最大、最小
timeFrameData: function(){ timeFrameData: function(){
let data = this.filterTimeframe.data.map(i=>({x:i.x,y:i.y})) let data = this.filterTimeframe.data.map(i=>({x:i.x,y:i.y}))
// y 軸斜率計算請參考 ./public/timeFrameSlope 的圖 // y 軸斜率計算請參考 ./public/timeFrameSlope 的圖
@@ -97,224 +90,25 @@ export default{
y: mf, y: mf,
}) })
let xVal = timeRange(data[0].x, data[data.length-1].x, 100);
let yVal = yTimeRange(data, 100); // y 還沒做切分份數
let timeData = xVal.map((x, index) => ({ x, y: yVal[index] }));
return data; return data;
// return timeData;
}, },
timeFrameTotal: function() {
return this.timeFrameData.length;
},
chartData: function() {
// !!畫面顯示的時間,資料轉換,在這邊最後一個步驟做就好!!
let labels = this.timeFrameData.map(time => getMoment(time.x).format("YYYY-MM-DD"));
let chartData = this.timeFrameData.map(item => {
// return item.y
return {
x: getMoment(item.x).format("YYYY-MM-DD"),
y: item.y,
}
})
const start = this.selectArea[0]-1;
const end = this.selectArea[1]-1;
const inside = (ctx, value) => ctx.p0DataIndex >= start && ctx.p1DataIndex <= end ? value : undefined;
const outside = (ctx, value) => ctx.p0DataIndex < start || ctx.p1DataIndex > end ? value : undefined;
return {
// 要呈現的資料
// labels:'Data',
datasets: [
{
label: 'Case', // 資料的標題標籤
// data: chartData,
data: this.timeFrameData,
fill: true,
showLine: false,
tension: 0.4,
backgroundColor: 'rgba(203, 213, 225)',
pointRadius: 0,
spanGaps: true,
segment: {
backgroundColor: ctx => inside(ctx, 'rgb(0,153,255)') || outside(ctx, 'rgb(203, 213, 225)'),
// backgroundColor: ctx => inside(ctx, 'rgb(245,40,145)') || outside(ctx, 'rgb(250, 196, 224)'),
},
x: 'x',
y: 'y',
},
]
};
},
options: function() {
const max = this.filterTimeframe.y_axis.max * 1.1;
return {
onResize: (chart, size) => {
this.resizeMask(chart, 0.38, 0.72);
},
responsive: true,
maintainAspectRatio: false,
layout: {
padding: {
top: 16,
left: 8,
right: 8,
}
},
plugins: {
legend: false, // 圖例
filler: {
propagate: false
},
},
animations: false, // 取消動畫
interaction: {
intersect: false,
},
scales: {
x: {
type: 'time',
ticks: {
color: '#0f172a',
display: true,
},
grid: {
display: false, // 隱藏 x 軸網格
},
},
y: {
beginAtZero: true, // scale 包含 0
max: max,
ticks: { // 設定間隔數值
display: false, // 隱藏數值,只顯示格線
stepSize: max / 4,
},
grid: {
color: 'rgba(100,116,139)',
z: 1,
},
border: {
display: false, // 隱藏左側多出來的線
}
},
},
}
},
config: function() {
const data = {
datasets: [
{
label: 'Case',
data: this.timeFrameData,
fill: 'start',
showLine: false,
tension: 0.4,
backgroundColor: 'rgba(0,153,255)',
pointRadius: 0,
x: 'x',
y: 'y',
}
]
};
return {
type: 'line',
data: data,
options: this.options,
};
}
}, },
methods: { methods: {
/** resizeMask(chart) {
* Set bar chart Options let from = (this.selectArea[0] * 0.01) / (this.selectRange * 0.01);
*/ let to = (this.selectArea[1] * 0.01) / (this.selectRange * 0.01);
setChartOptions() {
// 給取並取得 chart canvas 的 ID
// this.$nextTick(() => {
// const canvasId = this.$refs.TimeChart.$el;
// const chartCanvasId = canvasId.querySelector('canvas');
// chartCanvasId.setAttribute('id', 'chartCanvas');
// });
// console.log(chartCanvasId);
// 找出 y 軸最大值,乘以 1.1 讓圖的上方多一點空間
const max = this.filterTimeframe.y_axis.max * 1.1;
const config = { // 自訂屬性設定
// onResize: (chart, size) => {
// this.resizeMask(chart, 0.38, 0.72);
// },
// responsive: true,
maintainAspectRatio: false,
aspectRatio: 0.6,
layout: {
padding: {
top: 16,
left: 8,
right: 8,
}
},
plugins: {
legend: false, // 圖例
filler: {
propagate: false
},
},
animations: false, // 取消動畫
scales: {
x: {
type: 'time',
time: {
tooltipFormat: 'DD T',
},
ticks: {
color: '#0f172a',
display: true,
},
grid: {
display: false, // 隱藏 x 軸網格
},
},
y: {
beginAtZero: true, // scale 包含 0
max: max,
ticks: { // 設定間隔數值
display: false, // 隱藏數值,只顯示格線
stepSize: max / 4,
},
grid: {
color: 'rgba(100,116,139)',
z: 1,
},
border: {
display: false, // 隱藏左側多出來的線
}
},
},
};
// const myChart = new Chart(chartCanvasId, config);
// this.resizeMask(myChart, 0.38, 0.72);
// myChart.destroy()
return config
},
resizeMask(chart, from, to) {
console.log('resizeMask');
if (chart.chartArea === undefined) return;
this.resizeLeftMask(chart, from); this.resizeLeftMask(chart, from);
this.resizeRightMask(chart, to); this.resizeRightMask(chart, to);
}, },
resizeLeftMask(chart, from) { resizeLeftMask(chart, from) {
console.log('resizeLeftMask');
const canvas = document.getElementById("chartCanvasId"); const canvas = document.getElementById("chartCanvasId");
const mask = document.getElementById("chart-mask-left"); const mask = document.getElementById("chart-mask-left");
console.log(canvas);
mask.style.left = `${canvas.offsetLeft + chart.chartArea.left}px`; mask.style.left = `${canvas.offsetLeft + chart.chartArea.left}px`;
mask.style.width = `${chart.chartArea.width * from}px`; mask.style.width = `${chart.chartArea.width * from}px`;
mask.style.top = `${canvas.offsetTop + chart.chartArea.top}px`; mask.style.top = `${canvas.offsetTop + chart.chartArea.top}px`;
mask.style.height = `${chart.chartArea.height}px`; mask.style.height = `${chart.chartArea.height}px`;
}, },
resizeRightMask(chart, to) { resizeRightMask(chart, to) {
console.log('resizeRightMask');
const canvas = document.getElementById("chartCanvasId"); const canvas = document.getElementById("chartCanvasId");
const mask = document.getElementById("chart-mask-right"); const mask = document.getElementById("chart-mask-right");
mask.style.left = `${canvas.offsetLeft + chart.chartArea.left + chart.chartArea.width * to}px`; mask.style.left = `${canvas.offsetLeft + chart.chartArea.left + chart.chartArea.width * to}px`;
@@ -322,7 +116,7 @@ export default{
mask.style.top = `${canvas.offsetTop + chart.chartArea.top}px`; mask.style.top = `${canvas.offsetTop + chart.chartArea.top}px`;
mask.style.height = `${chart.chartArea.height}px`; mask.style.height = `${chart.chartArea.height}px`;
}, },
createChart(start, end) { createChart() {
const max = this.filterTimeframe.y_axis.max * 1.1; const max = this.filterTimeframe.y_axis.max * 1.1;
const data = { const data = {
@@ -341,9 +135,6 @@ export default{
] ]
}; };
const options = { const options = {
onResize: (chart, size) => {
this.resizeMask(chart, start, end);
},
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
layout: { layout: {
@@ -358,10 +149,16 @@ export default{
filler: { filler: {
propagate: false propagate: false
}, },
title: false
},
// animations: false, // 取消動畫
animation: {
onComplete: e => {
this.resizeMask(e.chart);
}
}, },
animations: false, // 取消動畫
interaction: { interaction: {
intersect: false, intersect: true,
}, },
scales: { scales: {
x: { x: {
@@ -398,30 +195,19 @@ export default{
}; };
this.canvasId = document.getElementById("chartCanvasId"); this.canvasId = document.getElementById("chartCanvasId");
this.chart = new Chart(this.canvasId, config); this.chart = new Chart(this.canvasId, config);
this.resizeMask(this.chart, start, end);
}, },
/** /**
* 滑塊改變的時候 * 滑塊改變的時候
* @param {array} e [1, 100] * @param {array} e [1, 100]
*/ */
changeSelectArea(e) { changeSelectArea(e) {
// const canvas = document.getElementById("chartCanvasId"); this.resizeMask(this.chart)
// window.chartCanvasId.destroy();
// const chart = new Chart(canvas, this.config);
// const chart = new Chart(this.canvasId, this.config);
// this.resizeMask(this.chart, e[0]/10, e[1]/10);
this.chart.destroy()
this.createChart(e[0]*0.01, e[1]*0.01)
}, },
}, },
mounted() { mounted() {
Chart.register(...registerables); Chart.register(...registerables);
this.createChart(0.38, 0.72); this.createChart();
// this.chartOptions = this.setChartOptions(); this.selectArea = [0, this.selectRange]
// this.selectArea = [1, this.timeFrameTotal];
this.selectArea = [38, 72]
}, },
} }
</script> </script>

View File

@@ -1,179 +0,0 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="imacat" />
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.3.0/dist/chart.umd.min.js "></script>
<title></title>
<style>
#chart-container {
position: relative;
margin: 2rem 4rem;
padding: 20px 40px;
border: 1px solid black;
width: 60%;
height: 60%;
}
#chart-canvas {
border: 1px solid red;
}
#chart-mask-left, #chart-mask-right {
position: absolute;
background-color: rgba(255, 255, 255, 0.5);
}
</style>
<script>
document.addEventListener("DOMContentLoaded", () => initializeChart());
let chart = null;
function initializeChart() {
const canvas = document.getElementById("chart-canvas");
const values = [22, 25, 37, 44, 52, 63, 42, 37, 20, 14, 8, 16];
const data = {
labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
datasets: [
{
label: 'Dataset',
data: values,
borderColor: "#FF0000",
backgroundColor: "#FF000080",
fill: "start",
}
]
};
const config = {
type: 'line',
data: data,
options: {
plugins: {
filler: {
propagate: false,
},
title: {
display: true,
text: (ctx) => 'Fill: ' + ctx.chart.data.datasets[0].fill
}
},
interaction: {
intersect: false,
},
elements: {
line: {
tension: 0.4,
},
},
responsive: true,
onResize: function(chart, size) {
resizeMask(chart, 0.38, 0.72);
},
maintainAspectRatio: false,
},
};
const chart = new Chart(canvas, config);
resizeMask(chart, 0.38, 0.72);
}
function resizeMask(chart, from, to) {
if (chart.chartArea === undefined) {
return;
}
resizeLeftMask(chart, from);
resizeRightMask(chart, to);
}
function resizeLeftMask(chart, from) {
const canvas = document.getElementById("chart-canvas");
const mask = document.getElementById("chart-mask-left");
mask.style.left = `${canvas.offsetLeft + chart.chartArea.left}px`;
mask.style.width = `${chart.chartArea.width * from}px`;
mask.style.top = `${canvas.offsetTop + chart.chartArea.top}px`;
mask.style.height = `${chart.chartArea.height}px`;
}
function resizeRightMask(chart, to) {
const canvas = document.getElementById("chart-canvas");
const mask = document.getElementById("chart-mask-right");
mask.style.left = `${canvas.offsetLeft + chart.chartArea.left + chart.chartArea.width * to}px`;
mask.style.width = `${chart.chartArea.width * (1 - to)}px`;
mask.style.top = `${canvas.offsetTop + chart.chartArea.top}px`;
mask.style.height = `${chart.chartArea.height}px`;
}
</script>
</head>
<body>
<div id="chart-container">
<canvas id="chart-canvas"></canvas>
<div id="chart-mask-left"></div>
<div id="chart-mask-right"></div>
</div>
</body>
</html>
<script>
import Chart from 'chart.js';
import 'chartjs-plugin-datalabels';
const originalXValues = ['2020-01-01', '2020-12-01'];
const originalYValues = [1, 10];
const numNewValues = 10;
// 将日期字符串转换为日期对象
const parseDate = (dateStr) => {
const [year, month, day] = dateStr.split('-');
return new Date(year, month - 1, day);
};
// 计算两个日期之间的天数差
const getDaysDiff = (date1, date2) => {
const oneDay = 24 * 60 * 60 * 1000; // 一天的毫秒数
return Math.round(Math.abs((date1 - date2) / oneDay));
};
// 计算新的 x 值
const interpolateXValues = (xValues, numNewValues) => {
const startDate = parseDate(xValues[0]);
const endDate = parseDate(xValues[1]);
const daysDiff = getDaysDiff(startDate, endDate);
const step = daysDiff / (numNewValues + 1);
const newValues = [];
for (let i = 1; i <= numNewValues; i++) {
const newDate = new Date(startDate.getTime() + step * i * 24 * 60 * 60 * 1000);
const formattedDate = newDate.toISOString().substring(0, 10);
newValues.push(formattedDate);
}
return newValues;
};
// 创建 Chart.js 图表
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: originalXValues,
datasets: [{
label: 'Data',
data: originalYValues,
backgroundColor: 'rgba(0, 123, 255, 0.5)',
borderColor: 'rgba(0, 123, 255, 1)',
borderWidth: 1
}]
},
options: {
plugins: {
datalabels: {
display: false
}
},
// 使用 beforeUpdate 钩子函数修改数据集
beforeUpdate: (chart) => {
const dataset = chart.data.datasets[0];
const newValues = interpolateXValues(originalXValues, numNewValues);
dataset.data = dataset.data.concat(new Array(numNewValues).fill(null));
chart.data.labels = chart.data.labels.concat(newValues);
}
}
});
</script>

View File

@@ -1,121 +0,0 @@
/**
* 將一段時間均分成多段的時間點
* @param {string} startTime 開始時間(ex:2017-10-05)
* @param {string} endTime 結束時間(ex:2017-10-06)
* @param {number} amount 切分成多少段
* @returns {array} 均分成多段的時間 array
*/
export function timeRange(minTime, maxTime, amount) {
// x 軸(時間軸)的範圍是最大-最小,從最小值按照 index 累加間距到最大值
const startTime = Date.parse(minTime)/1000; // 計算開始時間的時間戳記(單位:秒)
const endTime = Date.parse(maxTime)/1000; // 計算結束時間的時間戳記(單位:秒)
const timeRange = []; // return數據初始化
const timeGap = (endTime - startTime) / (amount - 1); // 切割後的時間間格
for (let i = 0; i < amount; i++) {
timeRange.push(startTime + timeGap * i);
}
return timeRange;
// min: this.timeFrameData[0].x
// max: this.timeFrameData[this.timeFrameData.length-1].x
}
/**
* 將 y 軸的值分割成跟 x 時間軸一樣的等份,使用統計學 R 語言算出貝茲曲線攻勢
* @param {array} data 切分成多少段
* @param {number} yAmount 切分成多少段
* @returns {array} 均分成多段的時間 array
*/
export function yTimeRange(data, yAmount) {
const yRange = [];
// 貝茲曲線公式
const threebsr = function (t, a1, a2, a3, a4) {
return (
(1 - t) * (1 - t) * (1 - t) * a1 +
3 * t * (1 - t)* (1 - t) * a2 +
3 * t * t * (1 - t) * a3 +
t * t * t * a4
)
}
// console.log(data);
// y min 切 5 份
for (let i = 0; i <= 1; i += 0.25) {
yRange.push(threebsr(i, data[0].y, data[0].y, data[1].y, data[1].y));
}
// console.log(yRange);
// y middle 每段切 6 份,從 0.1 開始
for (let j = 1; j < data.length - 2; j++) {
for (let i = 0.1; i <= 1; i += 0.1) {
yRange.push(threebsr(i, data[j].y, data[j].y, data[j+1].y, data[j+1].y));
}
// console.log(yRange);
}
// y max
for (let i = 0.2; i <= 1; i += 0.2) {
yRange.push(threebsr(i, data[data.length - 2].y, data[data.length - 2].y, data[data.length - 1].y, data[data.length - 1].y));
}
// console.log(yRange);
return yRange;
// y min
// const yMinPercent = 0.05;
// const yMinGap = (data[1].y-data[0].y) / (yAmount * yMinPercent - 1) ;
// for (let i = 0; i < yAmount * yMinPercent; i++) {
// // yRange.push(data[0].y + yMaxGap * i);
// }
// // y middle
// const yMiddlePercent = 0.11;
// for (let j = 1; j < data.length - 2; j++) {
// const yMiddleGap = (data[j + 1].y - data[j].y) / (yAmount * yMiddlePercent - 1);
// for (let i = 1; i < yAmount * yMiddlePercent; i++) {
// yRange.push(data[j].y + yMiddleGap * i);
// }
// }
// // y max
// const yMaxPercent = 0.06;
// const yMaxGap = (data[data.length - 1].y - data[data.length - 2].y) / (yAmount * yMaxPercent - 1);
// for (let i = 1; i < yAmount * yMaxPercent; i++) {
// yRange.push(data[data.length - 2].y + yMaxGap * i);
// }
// return yRange;
// // data: this.timeFrameData
// 貝茲曲線公式
// let threebsr = function (t, a1, a2, a3, a4) {
// return (
// (1 - t) * (1 - t) * (1 - t) * a1 +
// 3 * t * (1 - t)* (1 - t) * a2 +
// 3 * t * t * (1 - t) * a3 +
// t * t * t * a4
// )
// };
// y min
// let y = [
// threebsr(0, 448, 448, 429, 429),
// threebsr(0.1, 448, 448, 429, 429),
// threebsr(0.2, 448, 448, 429, 429),
// threebsr(0.3, 448, 448, 429, 429),
// threebsr(0.4, 448, 448, 429, 429),
// threebsr(0.5, 448, 448, 429, 429),
// threebsr(0.6, 448, 448, 429, 429),
// threebsr(0.7, 448, 448, 429, 429),
// threebsr(0.8, 448, 448, 429, 429),
// threebsr(0.9, 448, 448, 429, 429),
// threebsr(1, 448, 448, 429, 429),
// ]
// let y = []
// for (let i = 0; i <= 1; i += 0.25) {
// y.push(threebsr(i, 448, 448, 429, 429));
// }
// console.log(y);
}