Drag Chart
基于 ECharts 的可拖拽时间范围选择器,用于数据可视化和时间区间选择。
特性
- 📊 ECharts 集成 - 基于强大的 ECharts 图表库
- 🎯 拖拽选择 - 直观的拖拽操作选择时间范围
- ⏰ 时间控制 - 精确的时间范围控制
- 🎨 可定制 - 丰富的样式和配置选项
- 📱 响应式 - 适配不同屏幕尺寸
- 🔧 事件系统 - 完整的事件回调支持
安装
bash
npm install vue3-xm
在线演示
基础时间选择
基础时间范围选择
这是一个基础的拖拽时间范围选择示例,展示默认配置的使用方法。
选择的时间范围:
开始时间:
结束时间:
查看代码
vue
<template>
<div class="demo-container">
<h3>基础时间范围选择</h3>
<p>这是一个基础的拖拽时间范围选择示例,展示默认配置的使用方法。</p>
<div class="chart-wrapper">
<DragChart :data="basicData" :width="800" :height="400" @range-change="onRangeChange" />
</div>
<div class="result-display">
<h4>选择的时间范围:</h4>
<p>开始时间: {{ selectedRange.start }}</p>
<p>结束时间: {{ selectedRange.end }}</p>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
import { DragChart } from "vue3-xm";
// 基础演示数据
const basicData = ref([
{ time: "2024-01-01", value: 120 },
{ time: "2024-01-02", value: 132 },
{ time: "2024-01-03", value: 101 },
{ time: "2024-01-04", value: 134 },
{ time: "2024-01-05", value: 90 },
{ time: "2024-01-06", value: 230 },
{ time: "2024-01-07", value: 210 },
{ time: "2024-01-08", value: 320 },
{ time: "2024-01-09", value: 182 },
{ time: "2024-01-10", value: 191 },
{ time: "2024-01-11", value: 234 },
{ time: "2024-01-12", value: 290 },
{ time: "2024-01-13", value: 330 },
{ time: "2024-01-14", value: 310 },
{ time: "2024-01-15", value: 123 },
]);
// 选择的时间范围
const selectedRange = reactive({
start: "",
end: "",
});
// 处理范围变化
const onRangeChange = (range) => {
selectedRange.start = range.start;
selectedRange.end = range.end;
console.log("时间范围变化:", range);
};
</script>
<style scoped>
.demo-container {
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin: 20px 0;
background: #fafafa;
}
.chart-wrapper {
margin: 20px 0;
background: white;
padding: 20px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
height: 200px;
}
.result-display {
background: #f0f8ff;
padding: 15px;
border-radius: 6px;
border-left: 4px solid #007bff;
}
.result-display h4 {
margin: 0 0 10px 0;
color: #333;
}
.result-display p {
margin: 5px 0;
font-family: monospace;
color: #666;
}
</style>
高级配置演示
自定义配置的时间范围选择
这个示例展示了更多自定义配置选项,包括颜色主题、初始范围和事件处理。
当前选择范围
开始: 2024/01/05
结束: 2024/01/25
天数: 21 天
查看代码
vue
<template>
<div class="demo-container">
<h3>自定义配置的时间范围选择</h3>
<p>这个示例展示了更多自定义配置选项,包括颜色主题、初始范围和事件处理。</p>
<div class="controls">
<button @click="changeTheme" class="theme-btn">切换主题 (当前: {{ currentTheme }})</button>
<button @click="resetRange" class="reset-btn">重置范围</button>
</div>
<div class="chart-wrapper">
<DragChart
:data="advancedData"
:width="900"
:height="200"
:theme="currentTheme"
:initial-start="initialRange.start"
:initial-end="initialRange.end"
:show-tooltip="true"
:enable-zoom="true"
@range-change="onRangeChange"
@data-point-click="onDataPointClick"
/>
</div>
<div class="info-panel">
<div class="range-info">
<h4>当前选择范围</h4>
<div class="range-display"><span class="label">开始:</span> {{ formatDate(selectedRange.start) }}</div>
<div class="range-display"><span class="label">结束:</span> {{ formatDate(selectedRange.end) }}</div>
<div class="range-display"><span class="label">天数:</span> {{ calculateDaysDiff() }} 天</div>
</div>
<div class="click-info" v-if="clickedPoint">
<h4>点击的数据点</h4>
<p>时间: {{ clickedPoint.time }}</p>
<p>数值: {{ clickedPoint.value }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed } from "vue";
import { DragChart } from "vue3-xm";
// 高级演示数据 - 更多数据点
const advancedData = ref([
{ time: "2024-01-01", value: 120 },
{ time: "2024-01-02", value: 132 },
{ time: "2024-01-03", value: 101 },
{ time: "2024-01-04", value: 134 },
{ time: "2024-01-05", value: 90 },
{ time: "2024-01-06", value: 230 },
{ time: "2024-01-07", value: 210 },
{ time: "2024-01-08", value: 320 },
{ time: "2024-01-09", value: 182 },
{ time: "2024-01-10", value: 191 },
{ time: "2024-01-11", value: 234 },
{ time: "2024-01-12", value: 290 },
{ time: "2024-01-13", value: 330 },
{ time: "2024-01-14", value: 310 },
{ time: "2024-01-15", value: 123 },
{ time: "2024-01-16", value: 164 },
{ time: "2024-01-17", value: 201 },
{ time: "2024-01-18", value: 278 },
{ time: "2024-01-19", value: 312 },
{ time: "2024-01-20", value: 289 },
{ time: "2024-01-21", value: 267 },
{ time: "2024-01-22", value: 245 },
{ time: "2024-01-23", value: 198 },
{ time: "2024-01-24", value: 176 },
{ time: "2024-01-25", value: 203 },
{ time: "2024-01-26", value: 224 },
{ time: "2024-01-27", value: 256 },
{ time: "2024-01-28", value: 287 },
{ time: "2024-01-29", value: 298 },
{ time: "2024-01-30", value: 315 },
]);
// 主题配置
const themes = ["light", "dark", "colorful"];
const currentTheme = ref("light");
// 初始范围
const initialRange = reactive({
start: "2024-01-05",
end: "2024-01-25",
});
// 当前选择的范围
const selectedRange = reactive({
start: initialRange.start,
end: initialRange.end,
});
// 点击的数据点
const clickedPoint = ref(null);
// 切换主题
const changeTheme = () => {
const currentIndex = themes.indexOf(currentTheme.value);
const nextIndex = (currentIndex + 1) % themes.length;
currentTheme.value = themes[nextIndex];
};
// 重置范围
const resetRange = () => {
selectedRange.start = initialRange.start;
selectedRange.end = initialRange.end;
};
// 处理范围变化
const onRangeChange = (range) => {
selectedRange.start = range.start;
selectedRange.end = range.end;
};
// 处理数据点点击
const onDataPointClick = (point) => {
clickedPoint.value = point;
};
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return "--";
const date = new Date(dateStr);
return date.toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
};
// 计算天数差
const calculateDaysDiff = () => {
if (!selectedRange.start || !selectedRange.end) return 0;
const start = new Date(selectedRange.start);
const end = new Date(selectedRange.end);
const diffTime = Math.abs(end - start);
return Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
};
</script>
<style scoped>
.demo-container {
padding: 24px;
border: 1px solid #e0e0e0;
border-radius: 12px;
margin: 20px 0;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
.controls {
display: flex;
gap: 12px;
margin-bottom: 20px;
}
.theme-btn,
.reset-btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.theme-btn {
background: #007bff;
color: white;
}
.theme-btn:hover {
background: #0056b3;
}
.reset-btn {
background: #6c757d;
color: white;
}
.reset-btn:hover {
background: #545b62;
}
.chart-wrapper {
margin: 20px 0;
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
height: 200px;
}
.info-panel {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 20px;
}
.range-info,
.click-info {
background: white;
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.range-info h4,
.click-info h4 {
margin: 0 0 12px 0;
color: #333;
font-size: 16px;
border-bottom: 2px solid #007bff;
padding-bottom: 8px;
}
.range-display {
display: flex;
justify-content: space-between;
margin: 8px 0;
padding: 4px 0;
border-bottom: 1px solid #f0f0f0;
}
.label {
font-weight: bold;
color: #666;
}
.click-info p {
margin: 8px 0;
color: #666;
font-family: monospace;
}
@media (max-width: 768px) {
.info-panel {
grid-template-columns: 1fr;
}
.controls {
flex-direction: column;
}
}
</style>
基础用法
vue
<template>
<drag-chart
:time-range="timeRange"
:active-time="activeTime"
:value-data="valueData"
:symbol-size="20"
:cover-color="'rgba(160,210,255,0.14)'"
:line-color="'#5CB0FE'"
:max-range="168"
:min-range="3"
:need-click="true"
:auto-interval="true"
@update:activeTime="onActiveTimeUpdate"
@outOfRange="onOutOfRange"
/>
</template>
<script setup>
import { ref } from "vue";
import dayjs from "dayjs";
const timeRange = ref([dayjs().subtract(1, "day"), dayjs()]);
const activeTime = ref([0, 12]);
const valueData = ref([
// Array of values for each time slot
]);
function onActiveTimeUpdate(newActiveTime) {
activeTime.value = newActiveTime;
}
function onOutOfRange(event) {
console.log("Out of range:", event);
}
</script>
API
Props
Name | Type | Default | Description |
---|---|---|---|
timeRange | Array | [dayjs().subtract(1, 'day'), dayjs()] | X-axis start and end time range |
startIcon | String | LeftImg | Start drag handle icon |
endIcon | String | RightImg | End drag handle icon |
symbolSize | Number | 20 | Size of drag points |
valueData | Array | [] | Data values for chart bars |
activeTime | Array | [0, 12] | Current selected time range (hours) |
interval | Number | 4 | X-axis tick interval |
autoInterval | Boolean | true | Auto calculate interval (ignores interval prop) |
needClick | Boolean | true | Enable click to change position |
maxRange | Number | 168 (7 days) | Maximum selection range in hours |
minRange | Number | 3 | Minimum selection range in hours |
coverColor | String | 'rgba(160,210,255,0.14)' | Selection area background color |
lineColor | String | '#5CB0FE' | Selection line color |
Events
Name | Parameters | Description |
---|---|---|
update:activeTime | (activeTime: number[]) | Triggered when active time range changes |
outOfRange | (event: {type: string, currentRange: number, minRange: number}) | Triggered when drag exceeds range limits |
Slots
This component does not provide any slots.