diff --git a/devui/quadrant-diagram/config.ts b/devui/quadrant-diagram/config.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5902f086711dbf95db7f14e9dcd2342e327990b --- /dev/null +++ b/devui/quadrant-diagram/config.ts @@ -0,0 +1,41 @@ +export const QUADRANT_CONFIGS = []; +export const LABEL_SIZE = ['small', 'normal', 'large']; +export const DEFAULT_AXIS_CONFIGS = { + tickWidth: 10, + spaceBetweenLabelsAxis: 20, + xAxisLabel: '紧急度', + yAxisLabel: '重要度', + xAxisRange: { + min: 0, + max: 100, + step: 10 + }, + yAxisRange: { + min: 0, + max: 50, + step: 5 + }, + originPosition: { + left: 30, + bottom: 30 + }, + axisMargin: 35, + xWeight: 1, + yWeight: 1 +}; +export const DEFAULT_QUADRANT_CONFIGS = [ + { title: '重要紧急' }, + { title: '重要不紧急' }, + { title: '不重要不紧急' }, + { title: '不重要紧急' } +]; +export const AXIS_TITLE_SPACE = 15; +export const SMALL_LABEL_SIZE_CENTER_POINT = { + x: 6, y: 6 +}; +export const NORMAL_LABEL_SIZE_CENTER_POINT = { + x: 45, y: 14 +}; +export const LARGE_LABEL_SIZE_CENTER_POINT = { + x: 60, y: 18 +}; diff --git a/devui/quadrant-diagram/index.ts b/devui/quadrant-diagram/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..86648b6f0c465cd89ef3000d069ba21f0fef03df --- /dev/null +++ b/devui/quadrant-diagram/index.ts @@ -0,0 +1,16 @@ +import type { App } from 'vue' +import QuadrantDiagram from './src/quadrant-diagram' + +QuadrantDiagram.install = function (app: App) { + app.component(QuadrantDiagram.name, QuadrantDiagram) +} + +export { QuadrantDiagram } + +export default { + title: 'QuadrantDiagram 象限图', + category: '数据展示', + install(app: App): void { + app.use(QuadrantDiagram as any) + } +} diff --git a/devui/quadrant-diagram/src/components/axis/index.scss b/devui/quadrant-diagram/src/components/axis/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..b83d55a81572f72800e4f3b1d03e7f7f6da3331e --- /dev/null +++ b/devui/quadrant-diagram/src/components/axis/index.scss @@ -0,0 +1,3 @@ +canvas { + z-index: 1; +} diff --git a/devui/quadrant-diagram/src/components/axis/index.tsx b/devui/quadrant-diagram/src/components/axis/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f7353d000e7d83f8eb240a2b59036e853f2fc75d --- /dev/null +++ b/devui/quadrant-diagram/src/components/axis/index.tsx @@ -0,0 +1,224 @@ +import { defineComponent, toRefs, onMounted, ExtractPropTypes, reactive, ref } from 'vue' +import { IViewConfigs, IAxisConfigs } from '../../../type'; +import { AXIS_TITLE_SPACE } from '../../../config'; +import { quadrantDiagramAxisProps } from './types' +import { debounce } from 'lodash'; + +import './index.scss' + +const Axis = defineComponent({ + name: 'DQuadrantDiagramAxis', + props: quadrantDiagramAxisProps, + setup(props: ExtractPropTypes) { + + const { diagramId, view, axisConfigs } = toRefs(props); + const AXIS_COLOR = ref('#0000ff'); + const AXIS_LABEL_COLOR = ref('#ff0000'); + + const quadrantAxis = ref(); + const context = ref(); + const axisInnerAttr = reactive({ + axisOrigin: { + x: 0, + y: 0, + }, + axisTop: 0, + axisRight: 0, + axisWidth: 0, + axisHeight: 0, + yAxisTicksNum: 0, + xAxisTicksNum: 0, + xTickSpacing: 0, + yTickSpacing: 0, + }) + + const axisConfigsVal: IAxisConfigs = axisConfigs.value; + const viewVal: IViewConfigs = view.value; + + + onMounted(() => { + resetAxis(); + }); + + const resetAxis = debounce(() => { + initAxisData(); + setAxisData(); + drawAxis(); + drawAxisLabels(); + }, 200); + + /** + * 获取 canvas 并赋值宽高 + */ + const initAxisData = () => { + quadrantAxis.value = document.querySelector('#devui-quadrant-axis-' + diagramId.value); + quadrantAxis.value.width = viewVal.width; + quadrantAxis.value.height = viewVal.height; + } + + const setAxisData = () => { + context.value = quadrantAxis.value.getContext('2d'); + axisInnerAttr.axisOrigin = axisConfigsVal.axisOrigin; + axisInnerAttr.axisTop = axisConfigsVal.axisTop; + axisInnerAttr.axisRight = axisConfigsVal.axisRight; + axisInnerAttr.axisWidth = axisConfigsVal.axisWidth; + axisInnerAttr.axisHeight = axisConfigsVal.axisHeight; + axisInnerAttr.yAxisTicksNum = axisConfigsVal.yAxisTicksNum; + axisInnerAttr.xAxisTicksNum = axisConfigsVal.xAxisTicksNum; + axisInnerAttr.xTickSpacing = axisConfigsVal.xTickSpacing; + axisInnerAttr.yTickSpacing = axisConfigsVal.yTickSpacing; + } + + /** + * 执行绘制 + */ + const drawAxis = () => { + context.value.save(); + context.value.fillStyle = AXIS_COLOR.value; + context.value.strokeStyle = AXIS_COLOR.value; + drawXAxis(); + drawYAxis(); + context.value.lineWidth = 0.5; + drawXAxisTicks(); + drawYAxisTicks(); + context.value.restore(); + } + + /** + * 绘制 XY 轴 + */ + const drawYAxis = () => { + context.value.beginPath(); + context.value.moveTo(axisInnerAttr.axisOrigin.x, axisInnerAttr.axisOrigin.y); + context.value.lineTo(axisInnerAttr.axisOrigin.x, axisInnerAttr.axisTop - axisConfigsVal.axisMargin); + context.value.stroke(); + context.value.moveTo(axisInnerAttr.axisOrigin.x, axisInnerAttr.axisTop - axisConfigsVal.axisMargin); + context.value.lineTo(axisInnerAttr.axisOrigin.x + 5, axisInnerAttr.axisTop - axisConfigsVal.axisMargin + 10); + context.value.lineTo(axisInnerAttr.axisOrigin.x - 5, axisInnerAttr.axisTop - axisConfigsVal.axisMargin + 10); + context.value.fill(); + + } + const drawXAxis = () => { + context.value.beginPath(); + context.value.moveTo(axisInnerAttr.axisOrigin.x, axisInnerAttr.axisOrigin.y); + context.value.lineTo(axisInnerAttr.axisRight + axisConfigsVal.axisMargin - 10, axisInnerAttr.axisOrigin.y); + context.value.stroke(); + // 绘制坐标轴三角形 + context.value.moveTo(axisInnerAttr.axisRight + axisConfigsVal.axisMargin, axisInnerAttr.axisOrigin.y); + context.value.lineTo(axisInnerAttr.axisRight + axisConfigsVal.axisMargin - 10, axisInnerAttr.axisOrigin.y + 5); + context.value.lineTo(axisInnerAttr.axisRight + axisConfigsVal.axisMargin - 10, axisInnerAttr.axisOrigin.y - 5); + context.value.fill(); + } + + /** + * 绘制轴线刻度 + */ + const drawXAxisTicks = () => { + let deltaY; + for (let i = 1; i < axisInnerAttr.xAxisTicksNum; i++) { + context.value.beginPath(); + // 判断显示长刻度还是短刻度 + if (i % axisConfigsVal.xAxisRange.step === 0) { + deltaY = axisConfigsVal.tickWidth; + } else { + deltaY = axisConfigsVal.tickWidth / 2; + } + context.value.moveTo(axisInnerAttr.axisOrigin.x + i * axisInnerAttr.xTickSpacing, + axisInnerAttr.axisOrigin.y - deltaY); + context.value.lineTo(axisInnerAttr.axisOrigin.x + i * axisInnerAttr.xTickSpacing, + axisInnerAttr.axisOrigin.y + deltaY); + context.value.stroke(); + } + + } + const drawYAxisTicks = () => { + let deltaX; + for (let i = 1; i < axisInnerAttr.yAxisTicksNum; i++) { + context.value.beginPath(); + if (i % axisConfigsVal.yAxisRange.step === 0) { + deltaX = axisConfigsVal.tickWidth; + } else { + deltaX = axisConfigsVal.tickWidth / 2; + } + context.value.moveTo(axisInnerAttr.axisOrigin.x - deltaX, + axisInnerAttr.axisOrigin.y - i * axisInnerAttr.yTickSpacing); + context.value.lineTo(axisInnerAttr.axisOrigin.x + deltaX, + axisInnerAttr.axisOrigin.y - i * axisInnerAttr.yTickSpacing); + context.value.stroke(); + } + } + + const drawAxisLabels = () => { + context.value.save(); + context.value.fillStyle = AXIS_LABEL_COLOR.value; + drawXTicksLabels(); + drawYTicksLabels(); + context.value.restore(); + drawAxisTitle(); + } + + const drawXTicksLabels = () => { + context.value.textAlign = 'center'; + context.value.textBaseline = 'top'; + for (let i = 0; i <= axisInnerAttr.xAxisTicksNum; i++) { + if (i % axisConfigsVal.xAxisRange.step === 0) { + context.value.fillText(i, axisInnerAttr.axisOrigin.x + i * axisInnerAttr.xTickSpacing, + axisInnerAttr.axisOrigin.y + axisConfigsVal.spaceBetweenLabelsAxis); + } + } + }; + const drawYTicksLabels = () => { + context.value.textAlign = 'center'; + context.value.textBaseline = 'middle'; + for (let i = 0; i <= axisInnerAttr.yAxisTicksNum; i++) { + if (i % axisConfigsVal.yAxisRange.step === 0) { + context.value.fillText(i, axisInnerAttr.axisOrigin.x - axisConfigsVal.spaceBetweenLabelsAxis, + axisInnerAttr.axisOrigin.y - i * axisInnerAttr.yTickSpacing); + } + } + }; + const drawAxisTitle = () => { + context.value.font = '12px Microsoft YaHei'; + context.value.textAlign = 'left'; + context.value.fillStyle = AXIS_LABEL_COLOR.value; + const xLabelWidth = context.value.measureText(axisConfigsVal.xAxisLabel).width; + rotateLabel(axisConfigsVal.xAxisLabel, axisInnerAttr.axisRight + axisConfigsVal.axisMargin / 2, + axisInnerAttr.axisOrigin.y - xLabelWidth - AXIS_TITLE_SPACE); + context.value.fillText(axisConfigsVal.yAxisLabel, + axisInnerAttr.axisOrigin.x + AXIS_TITLE_SPACE, axisInnerAttr.axisTop - axisConfigsVal.axisMargin / 2); + }; + + const rotateLabel = (name: string, x: number, y: number) => { + for (let i = 0; i < name.length; i++) { + const str = name.slice(i, i + 1).toString(); + if (str.match(/[A-Za-z0-9]/)) { + context.value.save(); + context.value.translate(x, y); + context.value.rotate(Math.PI / 180 * 90); + context.value.textBaseline = 'bottom'; + context.value.fillText(str, 0, 0); + context.value.restore(); + y += context.value.measureText(str).width; + } else if (str.match(/[\u4E00-\u9FA5]/)) { + context.value.save(); + context.value.textBaseline = 'top'; + context.value.fillText(str, x, y); + context.value.restore(); + y += context.value.measureText(str).width; + } + } + } + + + }, + render() { + const { diagramId } = this; + return ( +
+ +
+ ); + } +}) + +export default Axis; \ No newline at end of file diff --git a/devui/quadrant-diagram/src/components/axis/types.ts b/devui/quadrant-diagram/src/components/axis/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..e22f059ed952e1fea802bb2a6fb6d1492aed35a6 --- /dev/null +++ b/devui/quadrant-diagram/src/components/axis/types.ts @@ -0,0 +1,16 @@ +import type { ExtractPropTypes, PropType } from 'vue' +import { IViewConfigs, IAxisConfigs } from '../../../type'; + +export const quadrantDiagramAxisProps = { + diagramId: { + type: String, + }, + axisConfigs: { + type: Object as PropType, + }, + view: { + type: Object as PropType, + }, +} as const + +export type QuadrantDiagramAxisProps = ExtractPropTypes \ No newline at end of file diff --git a/devui/quadrant-diagram/src/quadrant-diagram-types.ts b/devui/quadrant-diagram/src/quadrant-diagram-types.ts new file mode 100644 index 0000000000000000000000000000000000000000..3513e64f531df83a44387dd77b6b03e976b3f135 --- /dev/null +++ b/devui/quadrant-diagram/src/quadrant-diagram-types.ts @@ -0,0 +1,20 @@ +import type { ExtractPropTypes, PropType } from 'vue' +import { DEFAULT_AXIS_CONFIGS } from '../config'; +import { IViewConfigs, IAxisConfigs } from '../type'; + +export const quadrantDiagramProps = { + diagramId: { + type: String, + default: '1', + }, + axisConfigs: { + type: Object as PropType, + default: DEFAULT_AXIS_CONFIGS, + }, + view: { + type: Object as PropType, + default: { height: 720, width: 720 }, + }, +} as const + +export type QuadrantDiagramProps = ExtractPropTypes diff --git a/devui/quadrant-diagram/src/quadrant-diagram.tsx b/devui/quadrant-diagram/src/quadrant-diagram.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d5506a13465643435576d2673782ec8980542766 --- /dev/null +++ b/devui/quadrant-diagram/src/quadrant-diagram.tsx @@ -0,0 +1,62 @@ +import { defineComponent, toRefs, ref, onMounted, reactive } from 'vue' +import { quadrantDiagramProps, QuadrantDiagramProps } from './quadrant-diagram-types' +import DQuadrantDiagramAxis from './components/axis'; +import { DEFAULT_AXIS_CONFIGS } from '../config'; + +export default defineComponent({ + name: 'DQuadrantDiagram', + props: quadrantDiagramProps, + emits: [], + setup(props, ctx) { + const { diagramId, axisConfigs, view } = toRefs(props); + + const axisConfigsVal = axisConfigs.value; + const viewVal = view.value; + + const calAxisConfig = reactive({ + axisOrigin: { x: null, y: null }, + axisTop: null, + axisRight: null, + axisWidth: null, + axisHeight: null, + yAxisTicksNum: null, + xAxisTicksNum: null, + xTickSpacing: null, + yTickSpacing: null, + }) + + const initAxisData = () => { + const axisConfigKeys = Object.keys(DEFAULT_AXIS_CONFIGS); + for (let i = 0; i < axisConfigKeys.length; i++) { + if (calAxisConfig[axisConfigKeys[i]] === undefined) { + calAxisConfig[axisConfigKeys[i]] = DEFAULT_AXIS_CONFIGS[axisConfigKeys[i]]; + } + } + calAxisConfig.axisOrigin = { + x: axisConfigsVal.originPosition.left, + y: viewVal.height - axisConfigsVal.originPosition.bottom + }; + calAxisConfig.axisTop = axisConfigsVal.axisMargin; + calAxisConfig.axisRight = viewVal.width - axisConfigsVal.axisMargin; + calAxisConfig.axisWidth = calAxisConfig.axisRight - calAxisConfig.axisOrigin.x; + calAxisConfig.axisHeight = calAxisConfig.axisOrigin.y - calAxisConfig.axisTop; + calAxisConfig.yAxisTicksNum = axisConfigsVal.yAxisRange.max - axisConfigsVal.yAxisRange.min; + calAxisConfig.xAxisTicksNum = axisConfigsVal.xAxisRange.max - axisConfigsVal.xAxisRange.min; + calAxisConfig.xTickSpacing = calAxisConfig.axisWidth / calAxisConfig.xAxisTicksNum; + calAxisConfig.yTickSpacing = calAxisConfig.axisHeight / calAxisConfig.yAxisTicksNum; + } + + initAxisData(); + + return { diagramId, calAxisConfig, viewVal }; + }, + render() { + const { diagramId, calAxisConfig, viewVal } = this; + + return ( +
+ +
+ ) + } +}) diff --git a/devui/quadrant-diagram/type.ts b/devui/quadrant-diagram/type.ts new file mode 100644 index 0000000000000000000000000000000000000000..f7a13a7fcbb77078a806db72ea865a513e5fc15b --- /dev/null +++ b/devui/quadrant-diagram/type.ts @@ -0,0 +1,44 @@ +export interface IAxisConfigs { + tickWidth?: number // 刻度的宽(高)度,默认为10 + spaceBetweenLabelsAxis?: number // 刻度值和坐标轴之间的距离,默认为20 + axisMargin?: number // 右侧留出的空白区域 + xAxisLabel?: string // X轴名称,默认值为'紧急度' + yAxisLabel?: string // Y轴名称,默认值为'重要度' + xWeight?: number // X轴权重,默认值为1 + yWeight?: number // Y轴权重,默认值为1 + xAxisRange?: IRangeConfigs // X轴的坐标值范围和间距设置,默认值为{min:0,max:100,step:10} + yAxisRange?: IRangeConfigs // Y轴的坐标值范围和间距设置,默认值为{min:0,max:100,step:10} + originPosition?: { + left: number + bottom: number + } // 原点的位置设置,默认值为{left:30,bottom:30} + [propName: string]: any +} +export interface IQuadrantConfigs { + backgroundColor?: any + color?: any + title?: string + top?: number + left?: number +} +export interface ILabelDataConfigs { + x: number // X轴坐标值 + y: number // Y轴坐标值 + title: string // 标签的名称 + content?: string // 鼠标悬浮在标签上时的提示内容 + progress?: number // 标签对应事项的进度 + [propName: string]: any // 其他数据 +} + +export interface IViewConfigs { + height: number // 象限图高度 + width: number // 象限图宽度 +} + +export interface IRangeConfigs { + min: number // 坐标轴起始值 + max: number // 坐标轴终止值 + step: number // 坐标轴刻度值的间隔 +} + +export type labelSize = 'small' | 'normal' | 'large' diff --git a/sites/components/quadrant-diagram/index.md b/sites/components/quadrant-diagram/index.md new file mode 100644 index 0000000000000000000000000000000000000000..8a3e8f62cc821e67d3ce387c8bc575ec314e4f9b --- /dev/null +++ b/sites/components/quadrant-diagram/index.md @@ -0,0 +1,19 @@ +# ProQuadrantDiagramgress 象限图 + +象限图。 + +### 何时使用 + +根据需求对事务进行区域划分与价值排序,可用于管理事务的优先级。 + +### 基本用法 + +

Basic Usage

+ +:::demo + +```vue + +``` + +:::