Skip to content

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

NameTypeDefaultDescription
timeRangeArray[dayjs().subtract(1, 'day'), dayjs()]X-axis start and end time range
startIconStringLeftImgStart drag handle icon
endIconStringRightImgEnd drag handle icon
symbolSizeNumber20Size of drag points
valueDataArray[]Data values for chart bars
activeTimeArray[0, 12]Current selected time range (hours)
intervalNumber4X-axis tick interval
autoIntervalBooleantrueAuto calculate interval (ignores interval prop)
needClickBooleantrueEnable click to change position
maxRangeNumber168 (7 days)Maximum selection range in hours
minRangeNumber3Minimum selection range in hours
coverColorString'rgba(160,210,255,0.14)'Selection area background color
lineColorString'#5CB0FE'Selection line color

Events

NameParametersDescription
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.

Released under the MIT License.