diff --git a/docs/views/barChart/api/barChart.md b/docs/views/barChart/api/barChart.md
index a8b6532e4..5b0029b50 100644
--- a/docs/views/barChart/api/barChart.md
+++ b/docs/views/barChart/api/barChart.md
@@ -100,8 +100,9 @@ const chartData = {
| horizontal | Boolean | null | horizontal Bar 차트 표시를 위한 속성 | true / false |
| interval | String | null | 축에 표시되는 값의 간격 단위 (축의 타입에 따라 달라짐)
| labelStyle | Object | ([상세](#labelstyle)) | 라벨의 폰트 스타일을 설정 | |
- | formatter | function | null | 데이터가 표시되기 전에 데이터의 형식을 지정하는 데 사용 | (value) => value + '%' |
| plotLines | Array | ([상세](#plotline)) | plot line(임계선 표시 용도) 설정 | |
+ | plotBands | Array | ([상세](#plotband)) | plot band(임계영역 표시 용도) 설정 | |
+ | formatter | function | null | 데이터가 표시되기 전에 데이터의 형식을 지정하는 데 사용 | (value) => value + '%' |
##### linear type
- interval (Axis Label 표기를 위한 interval)
@@ -143,11 +144,20 @@ const chartData = {
| value | Number(value), Date, Number(Index) | null | 선을 표시할 위치에 해당하는 값 | 3000,
new Date(),
1 (축의 타입이 'step'인 경우 1번째 요소) |
| color | Hex, RGB, RGBA Code(String) | '#FF0000' | 선 색상 | |
| segments | Array | null | dash 간격 | [6, 2] |
-| label | Object | null | 표시할 label의 스타일을 정의 | ([상세](#plotlinelabel)) |
+| label | Object | null | 표시할 label의 스타일을 정의 | ([상세](#plotlabel)) |
+
+##### plotBand
+| 이름 | 타입 | 디폴트 | 설명 | 종류(예시) |
+|-----|------|-------|-----|-----|
+| from | Number(value), Date, Number(Index) | null | 박스를 표시할 시작 위치에 해당하는 값 | 3000,
new Date(),
1 (축의 타입이 'step'인 경우 1번째 요소) |
+| to | Number(value), Date, Number(Index) | null | 박스를 표시할 종료 위치에 해당하는 값 | 3000,
new Date(),
1 (축의 타입이 'step'인 경우 1번째 요소) |
+| color | Hex, RGB, RGBA Code(String) | '#FF0000' | 선 색상 | |
+| label | Object | null | 표시할 label의 스타일을 정의 | ([상세](#plotlabel)) |
-##### plotLineLabel
+##### plotLabel
| 이름 | 타입 | 디폴트 | 설명 | 종류(예시) |
|-----|------|-------|-----|-----|
+| show | Boolean | false | label 표시 여부 | true / false |
| fontSize | Number | 12 | 폰트 크기 | |
| fontColor | Hex, RGB, RGBA Code(String) | '#FF0000' | 폰트 색상 | |
| fillColor | Hex, RGB, RGBA Code(String) | '#FFFFFF' | 박스 배경 색상 | |
@@ -157,6 +167,8 @@ const chartData = {
| fontFamily | String | 'Roboto' | 폰트 스타일 | |
| textAlign | String | 'center' | 수평 정렬 | 'left', 'center', 'right' |
| verticalAlign | String | 'middle' | 수직 정렬 | 'top', 'middle', 'bottom' |
+| textOverflow | String | 'none' | 라벨을 넣을 수 있는 여백 혹은 maxWidth 값을 넘었을 경우의 처리방안 | 'none', 'ellipsis' |
+| maxWidth | Number | null | 라벨의 최대 너비 | |
#### title
| 이름 | 타입 | 디폴트 | 설명 | 종류(예시) |
diff --git a/docs/views/barChart/example/Column.vue b/docs/views/barChart/example/Column.vue
index 36cd4c9e5..622d4a389 100644
--- a/docs/views/barChart/example/Column.vue
+++ b/docs/views/barChart/example/Column.vue
@@ -54,23 +54,6 @@
startToZero: true,
autoScaleRatio: 0.1,
showGrid: false,
- plotLines: [{
- value: 180,
- color: '#FFA500',
- label: {
- text: 'Warning',
- fontColor: '#FFA500',
- },
- }, {
- value: 300,
- label: {
- lineWidth: 1,
- lineColor: '#000000',
- fillColor: '#FF0000',
- fontColor: '#FFFFFF',
- text: 'Critical',
- },
- }],
}],
};
diff --git a/docs/views/barChart/example/Horizontal.vue b/docs/views/barChart/example/Horizontal.vue
index d12d899c1..42518542e 100644
--- a/docs/views/barChart/example/Horizontal.vue
+++ b/docs/views/barChart/example/Horizontal.vue
@@ -64,7 +64,6 @@
},
tooltip: {
use: true,
- formatter: addUnit,
},
};
diff --git a/docs/views/barChart/example/PlotLine.vue b/docs/views/barChart/example/PlotLine.vue
new file mode 100644
index 000000000..656892731
--- /dev/null
+++ b/docs/views/barChart/example/PlotLine.vue
@@ -0,0 +1,105 @@
+
+
+
+
+
diff --git a/docs/views/barChart/props.js b/docs/views/barChart/props.js
index fc688d59f..e467b8efc 100644
--- a/docs/views/barChart/props.js
+++ b/docs/views/barChart/props.js
@@ -14,6 +14,8 @@ import Event from './example/Event';
import EventRaw from '!!raw-loader!./example/Event';
import Gradient from './example/Gradient';
import GradientRaw from '!!raw-loader!./example/Gradient';
+import PlotLine from './example/PlotLine';
+import PlotLineRaw from '!!raw-loader!./example/PlotLine';
export default {
mdText,
@@ -53,5 +55,10 @@ export default {
component: Gradient,
parsedData: parseComponent(GradientRaw),
},
+ 'Plot line & Plot band': {
+ description: '차트 배경에 선 및 영역을 표시할 수 있습니다.',
+ component: PlotLine,
+ parsedData: parseComponent(PlotLineRaw),
+ },
},
};
diff --git a/docs/views/lineChart/api/lineChart.md b/docs/views/lineChart/api/lineChart.md
index e67517941..3ebd09c2c 100644
--- a/docs/views/lineChart/api/lineChart.md
+++ b/docs/views/lineChart/api/lineChart.md
@@ -85,7 +85,7 @@ const chartData =
| interval | String | null | 축에 표시되는 값의 간격 단위 (ex. 'day', 'hour', 'minute'...)
| labelStyle | Object | ([상세](#labelstyle)) | 라벨의 폰트 스타일을 설정 | |
| plotLines | Array | ([상세](#plotline)) | plot line(임계선 표시 용도) 설정 | |
-
+ | plotBands | Array | ([상세](#plotband)) | plot band(임계영역 표시 용도) 설정 | |
| formatter | function | null | 데이터가 표시되기 전에 데이터의 형식을 지정하는 데 사용 | (value) => value + '%' |
##### time type
@@ -111,11 +111,20 @@ const chartData =
| value | Number(value), Date, Number(Index) | null | 선을 표시할 위치에 해당하는 값 | 3000,
new Date(),
1 (축의 타입이 'step'인 경우 1번째 요소) |
| color | Hex, RGB, RGBA Code(String) | '#FF0000' | 선 색상 | |
| segments | Array | null | dash 간격 | [6, 2] |
-| label | Object | null | 표시할 label의 스타일을 정의 | ([상세](#plotlinelabel)) |
+| label | Object | null | 표시할 label의 스타일을 정의 | ([상세](#plotlabel)) |
-##### plotLineLabel
+##### plotBand
| 이름 | 타입 | 디폴트 | 설명 | 종류(예시) |
|-----|------|-------|-----|-----|
+| from | Number(value), Date, Number(Index) | null | 박스를 표시할 시작 위치에 해당하는 값 | 3000,
new Date(),
1 (축의 타입이 'step'인 경우 1번째 요소) |
+| to | Number(value), Date, Number(Index) | null | 박스를 표시할 종료 위치에 해당하는 값 | 3000,
new Date(),
1 (축의 타입이 'step'인 경우 1번째 요소) |
+| color | Hex, RGB, RGBA Code(String) | '#FF0000' | 선 색상 | |
+| label | Object | null | 표시할 label의 스타일을 정의 | ([상세](#plotlabel)) |
+
+##### plotLabel
+| 이름 | 타입 | 디폴트 | 설명 | 종류(예시) |
+|-----|------|-------|-----|-----|
+| show | Boolean | false | label 표시 여부 | true / false |
| fontSize | Number | 12 | 폰트 크기 | |
| fontColor | Hex, RGB, RGBA Code(String) | '#FF0000' | 폰트 색상 | |
| fillColor | Hex, RGB, RGBA Code(String) | '#FFFFFF' | 박스 배경 색상 | |
@@ -125,6 +134,8 @@ const chartData =
| fontFamily | String | 'Roboto' | 폰트 스타일 | |
| textAlign | String | 'center' | 수평 정렬 | 'left', 'center', 'right' |
| verticalAlign | String | 'middle' | 수직 정렬 | 'top', 'middle', 'bottom' |
+| textOverflow | String | 'none' | 라벨을 넣을 수 있는 여백 혹은 maxWidth 값을 넘었을 경우의 처리방안 | 'none', 'ellipsis' |
+| maxWidth | Number | null | 라벨의 최대 너비 | |
#### title
| 이름 | 타입 | 디폴트 | 설명 | 종류(예시) |
diff --git a/docs/views/lineChart/example/Default.vue b/docs/views/lineChart/example/Default.vue
index 4405eb4dc..6fe0876d7 100644
--- a/docs/views/lineChart/example/Default.vue
+++ b/docs/views/lineChart/example/Default.vue
@@ -19,14 +19,21 @@
export default {
setup() {
- const crushTime = ref();
const chartData = reactive({
series: {
series1: { name: 'series#1' },
+ series2: { name: 'series#2' },
+ series3: { name: 'series#3' },
+ series4: { name: 'series#4' },
+ series5: { name: 'series#5' },
},
labels: [],
data: {
series1: [],
+ series2: [],
+ series3: [],
+ series4: [],
+ series5: [],
},
});
@@ -49,28 +56,12 @@
type: 'time',
timeFormat: 'HH:mm:ss',
interval: 'second',
- plotLines: [{
- color: '#FF0000',
- value: crushTime,
- segments: [6, 2],
- label: {
- text: 'Crush',
- },
- }],
}],
axesY: [{
type: 'linear',
showGrid: true,
startToZero: true,
autoScaleRatio: 0.1,
- plotLines: [{
- color: '#FFA500',
- value: 3000,
- label: {
- text: 'Caution',
- fontColor: '#FFA500',
- },
- }],
}],
});
@@ -91,12 +82,7 @@
seriesData.shift();
}
- const randomValue = Math.floor(Math.random() * ((5000 - 5) + 1)) + 5;
- seriesData.push(randomValue);
-
- if (randomValue > 4800) {
- crushTime.value = timeValue;
- }
+ seriesData.push(Math.floor(Math.random() * ((5000 - 5) + 1)) + 5);
});
};
diff --git a/docs/views/lineChart/example/PlotLine.vue b/docs/views/lineChart/example/PlotLine.vue
new file mode 100644
index 000000000..ca9a90a2e
--- /dev/null
+++ b/docs/views/lineChart/example/PlotLine.vue
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
diff --git a/docs/views/lineChart/props.js b/docs/views/lineChart/props.js
index 51c332eb4..ae25e3b76 100644
--- a/docs/views/lineChart/props.js
+++ b/docs/views/lineChart/props.js
@@ -12,6 +12,8 @@ import DragSelection from './example/DragSelection';
import DragSelectionRaw from '!!raw-loader!./example/DragSelection';
import Tooltip from './example/Tooltip';
import TooltipRaw from '!!raw-loader!./example/Tooltip';
+import PlotLine from './example/PlotLine';
+import PlotLineRaw from '!!raw-loader!./example/PlotLine';
export default {
mdText,
@@ -46,5 +48,10 @@ export default {
component: Tooltip,
parsedData: parseComponent(TooltipRaw),
},
+ 'Plot line & Plot band': {
+ description: '차트 배경에 선 및 영역을 표시할 수 있습니다.',
+ component: PlotLine,
+ parsedData: parseComponent(PlotLineRaw),
+ },
},
};
diff --git a/docs/views/scatterChart/api/scatterChart.md b/docs/views/scatterChart/api/scatterChart.md
index bedfb90e7..09319420f 100644
--- a/docs/views/scatterChart/api/scatterChart.md
+++ b/docs/views/scatterChart/api/scatterChart.md
@@ -78,6 +78,7 @@ const chartData =
| interval | String | null | 축에 표시되는 값의 간격 단위 (ex. 'day', 'hour', 'minute'...)
| labelStyle | Object | ([상세](#labelstyle)) | 라벨의 폰트 스타일을 설정 | |
| plotLines | Array | ([상세](#plotline)) | plot line(임계선 표시 용도) 설정 | |
+ | plotBands | Array | ([상세](#plotband)) | plot band(임계영역 표시 용도) 설정 | |
| formatter | function | null | 데이터가 표시되기 전에 데이터의 형식을 지정하는 데 사용 | (value) => value + '%' |
##### time type
@@ -103,11 +104,20 @@ const chartData =
| value | Number(value), Date, Number(Index) | null | 선을 표시할 위치에 해당하는 값 | 3000,
new Date(),
1 (축의 타입이 'step'인 경우 1번째 요소) |
| color | Hex, RGB, RGBA Code(String) | '#FF0000' | 선 색상 | |
| segments | Array | null | dash 간격 | [6, 2] |
-| label | Object | null | 표시할 label의 스타일을 정의 | ([상세](#plotlinelabel)) |
+| label | Object | null | 표시할 label의 스타일을 정의 | ([상세](#plotlabel)) |
-##### plotLineLabel
+##### plotBand
| 이름 | 타입 | 디폴트 | 설명 | 종류(예시) |
|-----|------|-------|-----|-----|
+| from | Number(value), Date, Number(Index) | null | 박스를 표시할 시작 위치에 해당하는 값 | 3000,
new Date(),
1 (축의 타입이 'step'인 경우 1번째 요소) |
+| to | Number(value), Date, Number(Index) | null | 박스를 표시할 종료 위치에 해당하는 값 | 3000,
new Date(),
1 (축의 타입이 'step'인 경우 1번째 요소) |
+| color | Hex, RGB, RGBA Code(String) | '#FF0000' | 선 색상 | |
+| label | Object | null | 표시할 label의 스타일을 정의 | ([상세](#plotlabel)) |
+
+##### plotLabel
+| 이름 | 타입 | 디폴트 | 설명 | 종류(예시) |
+|-----|------|-------|-----|-----|
+| show | Boolean | false | label 표시 여부 | true / false |
| fontSize | Number | 12 | 폰트 크기 | |
| fontColor | Hex, RGB, RGBA Code(String) | '#FF0000' | 폰트 색상 | |
| fillColor | Hex, RGB, RGBA Code(String) | '#FFFFFF' | 박스 배경 색상 | |
@@ -117,6 +127,8 @@ const chartData =
| fontFamily | String | 'Roboto' | 폰트 스타일 | |
| textAlign | String | 'center' | 수평 정렬 | 'left', 'center', 'right' |
| verticalAlign | String | 'middle' | 수직 정렬 | 'top', 'middle', 'bottom' |
+| textOverflow | String | 'none' | 라벨을 넣을 수 있는 여백 혹은 maxWidth 값을 넘었을 경우의 처리방안 | 'none', 'ellipsis' |
+| maxWidth | Number | null | 라벨의 최대 너비 | |
#### title
| 이름 | 타입 | 디폴트 | 설명 | 종류(예시) |
diff --git a/docs/views/scatterChart/example/PlotLine.vue b/docs/views/scatterChart/example/PlotLine.vue
new file mode 100644
index 000000000..12ae67d8f
--- /dev/null
+++ b/docs/views/scatterChart/example/PlotLine.vue
@@ -0,0 +1,190 @@
+
+
+
+
+
+ 데이터 자동 업데이트
+
+
+
+
+
+
+
+
+
diff --git a/docs/views/scatterChart/props.js b/docs/views/scatterChart/props.js
index 3e07a28bc..719814e84 100644
--- a/docs/views/scatterChart/props.js
+++ b/docs/views/scatterChart/props.js
@@ -4,6 +4,8 @@ import Default from './example/Default';
import DefaultRaw from '!!raw-loader!./example/Default';
import Event from './example/Event';
import EventRaw from '!!raw-loader!./example/Event';
+import PlotLine from './example/PlotLine';
+import PlotLineRaw from '!!raw-loader!./example/PlotLine';
export default {
mdText,
@@ -18,5 +20,10 @@ export default {
component: Event,
parsedData: parseComponent(EventRaw),
},
+ 'Plot line & Plot band': {
+ description: '차트 배경에 선 및 영역을 표시할 수 있습니다.',
+ component: PlotLine,
+ parsedData: parseComponent(PlotLineRaw),
+ },
},
};
diff --git a/src/components/chart/helpers/helpers.constant.js b/src/components/chart/helpers/helpers.constant.js
index 868bb3692..aa1c21243 100644
--- a/src/components/chart/helpers/helpers.constant.js
+++ b/src/components/chart/helpers/helpers.constant.js
@@ -102,6 +102,7 @@ export const PLOT_LINE_OPTION = {
};
export const PLOT_LINE_LABEL_OPTION = {
+ show: false,
fontSize: 12,
fontColor: '#FF0000',
fillColor: '#FFFFFF',
@@ -111,8 +112,15 @@ export const PLOT_LINE_LABEL_OPTION = {
fontFamily: 'Roboto',
verticalAlign: 'middle',
textAlign: 'center',
+ textOverflow: 'none', // 'none', 'ellipsis'
+ maxWidth: null,
};
+export const PLOT_BAND_OPTION = {
+ color: '#FAE59D',
+};
+
+
export const TIME_INTERVALS = {
millisecond: {
common: true,
diff --git a/src/components/chart/scale/scale.js b/src/components/chart/scale/scale.js
index d8b0f9514..d7023a5cc 100644
--- a/src/components/chart/scale/scale.js
+++ b/src/components/chart/scale/scale.js
@@ -5,6 +5,7 @@ import {
AXIS_UNITS,
PLOT_LINE_OPTION,
PLOT_LINE_LABEL_OPTION,
+ PLOT_BAND_OPTION,
} from '../helpers/helpers.constant';
import Util from '../helpers/helpers.util';
@@ -284,17 +285,48 @@ class Scale {
ctx.closePath();
}
- // Draw plot line
- if (this.plotLines?.length) {
+ // Draw plot lines and plot bands
+ if (this.plotBands?.length || this.plotLines?.length) {
const xArea = chartRect.chartWidth - (labelOffset.left + labelOffset.right);
const yArea = chartRect.chartHeight - (labelOffset.top + labelOffset.bottom);
const padding = aliasPixel + 1;
const minX = aPos.x1 + padding;
const maxX = aPos.x2;
- const minY = aPos.y1 + padding;
- const maxY = aPos.y2;
+ const minY = aPos.y1 + padding; // top
+ const maxY = aPos.y2; // bottom
- this.plotLines.forEach((plotLine) => {
+ this.plotBands?.forEach((plotBand) => {
+ if (!plotBand.from && !plotBand.to) {
+ return;
+ }
+
+ const mergedPlotBandOpt = defaultsDeep({}, plotBand, PLOT_BAND_OPTION);
+ const { from, to, label: labelOpt } = mergedPlotBandOpt;
+
+ this.setPlotBandStyle(mergedPlotBandOpt);
+
+ let fromPos;
+ let toPos;
+ if (this.type === 'x') {
+ fromPos = Canvas.calculateX(from ?? minX, axisMin, axisMax, xArea, minX);
+ toPos = Canvas.calculateX(to ?? maxX, axisMin, axisMax, xArea, minX);
+ this.drawXPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
+ } else {
+ fromPos = Canvas.calculateY(from ?? axisMin, axisMin, axisMax, yArea, maxY);
+ toPos = Canvas.calculateY(to ?? axisMax, axisMin, axisMax, yArea, maxY);
+ this.drawYPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
+ }
+
+ if (labelOpt.show) {
+ const labelOptions = this.getNormalizedLabelOptions(chartRect, labelOpt);
+ const textXY = this.getPlotBandLabelPosition(fromPos, toPos, labelOptions, maxX, minY);
+ this.drawPlotLabel(labelOptions, textXY);
+ }
+
+ ctx.restore();
+ });
+
+ this.plotLines?.forEach((plotLine) => {
if (!plotLine.value) {
return;
}
@@ -304,12 +336,19 @@ class Scale {
this.setPlotLineStyle(mergedPlotLineOpt);
+ let dataPos;
if (this.type === 'x') {
- const dataX = Canvas.calculateX(value, axisMin, axisMax, xArea, minX);
- this.drawXPlotLine(dataX, minX, maxX, minY, maxY, labelOpt);
+ dataPos = Canvas.calculateX(value, axisMin, axisMax, xArea, minX);
+ this.drawXPlotLine(dataPos, minX, maxX, minY, maxY);
} else {
- const dataY = Canvas.calculateY(value, axisMin, axisMax, yArea, maxY);
- this.drawYPlotLine(dataY, minX, maxX, minY, maxY, labelOpt);
+ dataPos = Canvas.calculateY(value, axisMin, axisMax, yArea, maxY);
+ this.drawYPlotLine(dataPos, minX, maxX, minY, maxY);
+ }
+
+ if (labelOpt.show) {
+ const labelOptions = this.getNormalizedLabelOptions(chartRect, labelOpt);
+ const textXY = this.getPlotLineLabelPosition(dataPos, labelOptions, maxX, minY);
+ this.drawPlotLabel(labelOptions, textXY);
}
ctx.restore();
@@ -337,6 +376,55 @@ class Scale {
}
}
+ /**
+ * Set plot band style
+ * @param {object} plotBand plotBand Options
+ *
+ * @returns {undefined}
+ */
+ setPlotBandStyle(plotBand) {
+ const ctx = this.ctx;
+ const { color } = plotBand;
+
+ ctx.beginPath();
+ ctx.save();
+ ctx.fillStyle = color;
+ }
+
+ /**
+ * Draw X Plot band
+ * @param {number} fromDataX From data's X Position
+ * @param {number} toDataX To data's X Position
+ * @param {number} minX Min X Position
+ * @param {number} maxX Max X Position
+ * @param {number} minY Min Y Position
+ * @param {number} maxY Max Y Position
+ *
+ * @returns {undefined}
+ */
+ drawXPlotBand(fromDataX, toDataX, minX, maxX, minY, maxY) {
+ const ctx = this.ctx;
+
+ const checkValidPosition = x => x || x > minX || x < maxX;
+
+ if (!checkValidPosition(fromDataX) || !checkValidPosition(toDataX)) {
+ ctx.closePath();
+ ctx.restore();
+ return;
+ }
+
+ ctx.moveTo(fromDataX, minY);
+ ctx.lineTo(fromDataX, maxY);
+ ctx.lineTo(toDataX, maxY);
+ ctx.lineTo(toDataX, minY);
+ ctx.lineTo(fromDataX, minY);
+
+ ctx.stroke();
+ ctx.fill();
+ ctx.restore();
+ ctx.closePath();
+ }
+
/**
* Draw X Plot line
* @param {object} dataX Data's X Position
@@ -344,11 +432,10 @@ class Scale {
* @param {number} maxX Max X Position
* @param {number} minY Min Y Position
* @param {number} maxY Max Y Position
- * @param {object} labelOpt plotLine Options
*
* @returns {undefined}
*/
- drawXPlotLine(dataX, minX, maxX, minY, maxY, labelOpt) {
+ drawXPlotLine(dataX, minX, maxX, minY, maxY) {
const ctx = this.ctx;
if (!dataX || dataX < minX || dataX > maxX) {
@@ -363,51 +450,6 @@ class Scale {
ctx.stroke();
ctx.restore();
ctx.closePath();
-
- if (labelOpt) {
- const mergedLabelOpt = defaultsDeep({}, labelOpt, PLOT_LINE_LABEL_OPTION);
-
- ctx.save();
- ctx.beginPath();
- ctx.font = Util.getLabelStyle(mergedLabelOpt);
-
- const {
- fontSize,
- labelBoxPadding,
- labelHalfWidth,
- } = this.getLabelParameters(mergedLabelOpt);
-
- if (fontSize <= 0) {
- return;
- }
-
- let textX;
- switch (mergedLabelOpt.textAlign) {
- case 'left':
- textX = dataX - labelHalfWidth - labelBoxPadding;
- break;
-
- case 'right':
- textX = dataX + labelHalfWidth + labelBoxPadding;
- break;
-
- case 'center':
- default:
- textX = dataX;
- break;
- }
-
- const textY = minY - labelBoxPadding - fontSize;
-
- this.drawPlotLineLabel(mergedLabelOpt, {
- top: minY - (labelBoxPadding * 2) - fontSize,
- bottom: minY - labelBoxPadding,
- left: textX - labelHalfWidth - labelBoxPadding,
- right: textX + labelHalfWidth + labelBoxPadding,
- x: textX,
- y: textY,
- });
- }
}
/**
@@ -417,11 +459,10 @@ class Scale {
* @param {number} maxX Max X Position
* @param {number} minY Min Y Position
* @param {number} maxY Max Y Position
- * @param {object} labelOpt plotLine Options
*
* @returns {undefined}
*/
- drawYPlotLine(dataY, minX, maxX, minY, maxY, labelOpt) {
+ drawYPlotLine(dataY, minX, maxX, minY, maxY) {
const ctx = this.ctx;
if (!dataY || dataY > maxY || dataY < minY) {
@@ -436,91 +477,263 @@ class Scale {
ctx.stroke();
ctx.restore();
ctx.closePath();
+ }
- if (labelOpt) {
- const mergedLabelOpt = defaultsDeep({}, labelOpt, PLOT_LINE_LABEL_OPTION);
+ /**
+ * Draw Y Plot band
+ * @param {number} fromDataY From data's Y Position (bottom)
+ * @param {number} toDataY To data's Y Position (top)
+ * @param {number} minX Min X Position
+ * @param {number} maxX Max X Position
+ * @param {number} minY Min Y Position
+ * @param {number} maxY Max Y Position
+ *
+ * @returns {undefined}
+ */
+ drawYPlotBand(fromDataY, toDataY, minX, maxX, minY, maxY) {
+ const ctx = this.ctx;
- ctx.save();
- ctx.beginPath();
- ctx.font = Util.getLabelStyle(mergedLabelOpt);
+ const checkValidPosition = y => y || y > minY || y < maxY;
+
+ if (!checkValidPosition(fromDataY) || !checkValidPosition(toDataY)) {
+ ctx.closePath();
+ ctx.restore();
+ return;
+ }
+
+ ctx.moveTo(minX, fromDataY);
+ ctx.lineTo(minX, toDataY);
+ ctx.lineTo(maxX, toDataY);
+ ctx.lineTo(maxX, fromDataY);
+ ctx.lineTo(minX, fromDataY);
+
+ ctx.fill();
+ ctx.restore();
+ ctx.closePath();
+ }
+
+ /**
+ * get normalized options for plot label
+ * @param {object} chartRect chartRect
+ * @param {object} labelOpt plotLine Options
+ *
+ * @returns {object}
+ */
+ getNormalizedLabelOptions(chartRect, labelOpt) {
+ const mergedLabelOpt = defaultsDeep({}, labelOpt, PLOT_LINE_LABEL_OPTION);
+
+ const ctx = this.ctx;
+ const { maxWidth } = mergedLabelOpt;
+ const fontSize = mergedLabelOpt.fontSize > 20 ? 20 : mergedLabelOpt.fontSize;
+ let label = mergedLabelOpt.text;
+ let labelWidth = maxWidth ?? ctx.measureText(label).width;
+
+ const plotLabelAreaWidth = this.type === 'y'
+ ? chartRect.width - chartRect.chartWidth
+ : maxWidth ?? chartRect.width;
+
+ if (plotLabelAreaWidth < ctx.measureText(label).width && mergedLabelOpt.textOverflow === 'ellipsis') {
+ label = Util.truncateLabelWithEllipsis(mergedLabelOpt.text, plotLabelAreaWidth, ctx);
+ labelWidth = ctx.measureText(label).width;
+ }
+
+ return {
+ label,
+ fontSize,
+ labelWidth,
+ labelBoxPadding: fontSize / 4,
+ labelHalfWidth: labelWidth / 2,
+ labelHalfHeight: fontSize / 2,
+ ...mergedLabelOpt,
+ };
+ }
+
+ /**
+ * Calculate position of plot band's label
+ * @param {object} fromPos from data position
+ * @param {object} toPos to data position
+ * @param {object} labelOpt label options
+ * @param {object} maxX max x position
+ * @param {object} minY min y position
+ *
+ * @returns {object}
+ */
+ getPlotBandLabelPosition(fromPos, toPos, labelOpt, maxX, minY) {
+ const {
+ fontSize,
+ labelWidth,
+ labelHalfWidth,
+ labelHalfHeight,
+ labelBoxPadding,
+ textAlign,
+ verticalAlign,
+ } = labelOpt;
+
+ if (fontSize <= 0) {
+ return { textX: 0, textY: 0 };
+ }
+
+ let textX;
+ let textY;
+
+ if (this.type === 'x') {
+ textY = minY - labelBoxPadding - fontSize;
- const {
- fontSize,
- labelWidth,
- labelHalfHeight,
- labelBoxPadding,
- } = this.getLabelParameters(mergedLabelOpt);
+ switch (textAlign) {
+ case 'left':
+ textX = fromPos + labelHalfWidth + labelBoxPadding;
+ break;
- if (fontSize <= 0) {
- return;
+ case 'right':
+ textX = toPos - labelHalfWidth - labelBoxPadding;
+ break;
+
+ case 'center':
+ default:
+ textX = ((toPos - fromPos) / 2) + fromPos;
+ break;
}
+ } else {
+ textX = maxX + labelWidth + labelBoxPadding;
- let textY;
- switch (mergedLabelOpt.verticalAlign) {
+ switch (verticalAlign) {
case 'top':
- textY = dataY - labelHalfHeight - labelBoxPadding;
+ textY = toPos + labelHalfHeight + labelBoxPadding;
break;
case 'bottom':
- textY = dataY + labelHalfHeight + labelBoxPadding;
+ textY = fromPos - labelHalfHeight - labelBoxPadding;
break;
case 'middle':
default:
- textY = dataY;
+ textY = ((fromPos - toPos) / 2) + toPos;
break;
}
-
- const textX = maxX + labelWidth + labelBoxPadding;
-
- this.drawPlotLineLabel(mergedLabelOpt, {
- top: textY - labelHalfHeight - labelBoxPadding,
- bottom: textY + labelHalfHeight + labelBoxPadding,
- left: textX - labelWidth - (labelBoxPadding / 2),
- right: textX + labelBoxPadding,
- x: textX,
- y: textY,
- });
}
+
+ return { textX, textY };
}
/**
- * Calculate Values for drawing label
- * @param {object} labelOpt plotLine Options
+ * Calculate position of plot line's label
+ * @param {object} dataPos data position
+ * @param {object} labelOpt label options
+ * @param {object} maxX max x position
+ * @param {object} minY min y position
*
- * @returns {object}
+ * @returns {undefined}
*/
- getLabelParameters(labelOpt) {
- const ctx = this.ctx;
- const fontSize = labelOpt.fontSize > 20 ? 20 : labelOpt.fontSize;
- const labelBoxPadding = fontSize / 4;
- const labelWidth = ctx.measureText(labelOpt.text).width;
- const labelHalfWidth = labelWidth / 2;
- const labelHalfHeight = fontSize / 2;
-
- return {
+ getPlotLineLabelPosition(dataPos, labelOpt, maxX, minY) {
+ const {
fontSize,
- labelBoxPadding,
labelWidth,
labelHalfWidth,
labelHalfHeight,
- };
+ labelBoxPadding,
+ } = labelOpt;
+
+ if (fontSize <= 0) {
+ return { textX: 0, textY: 0 };
+ }
+
+ let textX;
+ let textY;
+
+ if (this.type === 'x') {
+ textY = minY - labelBoxPadding - fontSize;
+
+ switch (labelOpt.textAlign) {
+ case 'left':
+ textX = dataPos - labelHalfWidth - labelBoxPadding;
+ break;
+
+ case 'right':
+ textX = dataPos + labelHalfWidth + labelBoxPadding;
+ break;
+
+ case 'center':
+ default:
+ textX = dataPos;
+ break;
+ }
+ } else {
+ textX = maxX + labelWidth + labelBoxPadding;
+
+ switch (labelOpt.verticalAlign) {
+ case 'top':
+ textY = dataPos - labelHalfHeight - labelBoxPadding;
+ break;
+
+ case 'bottom':
+ textY = dataPos + labelHalfHeight + labelBoxPadding;
+ break;
+
+ case 'middle':
+ default:
+ textY = dataPos;
+ break;
+ }
+ }
+
+ return { textX, textY };
}
/**
* Calculate Values for drawing label
- * @param {object} labelOpt plot line Label Options
- * @param {object} positions label positions
+ * @param {object} labelOptions plot line Label Options
+ * @param {object} positions x, y Position
*
* @returns {undefined}
*/
- drawPlotLineLabel(labelOpt, positions) {
+ drawPlotLabel(labelOptions, positions) {
+ if (!positions) {
+ return;
+ }
+
+ const { textX, textY } = positions;
+ const {
+ label,
+ fontSize,
+ fontColor,
+ fillColor,
+ lineColor,
+ lineWidth,
+ labelBoxPadding,
+ labelWidth,
+ labelHalfWidth,
+ labelHalfHeight,
+ } = labelOptions;
+
+ if (fontSize <= 0) {
+ return;
+ }
+
const ctx = this.ctx;
- const { top, bottom, left, right, x, y } = positions;
+ ctx.save();
+ ctx.beginPath();
+ ctx.font = Util.getLabelStyle(labelOptions);
+
+ let top = 0;
+ let bottom = 0;
+ let left = 0;
+ let right = 0;
- ctx.fillStyle = labelOpt.fillColor;
- ctx.strokeStyle = labelOpt.lineColor;
- ctx.lineWidth = labelOpt.lineWidth;
+ if (this.type === 'x') {
+ top = textY - labelBoxPadding;
+ bottom = textY + fontSize;
+ left = textX - labelHalfWidth - labelBoxPadding;
+ right = textX + labelHalfWidth + labelBoxPadding;
+ } else {
+ top = textY - labelHalfHeight - labelBoxPadding;
+ bottom = textY + labelHalfHeight + labelBoxPadding;
+ left = textX - labelWidth;
+ right = textX + labelBoxPadding;
+ }
+
+ ctx.fillStyle = fillColor;
+ ctx.strokeStyle = lineColor;
+ ctx.lineWidth = lineWidth;
ctx.moveTo(left, bottom);
ctx.lineTo(left, top);
ctx.lineTo(right, top);
@@ -528,12 +741,13 @@ class Scale {
ctx.lineTo(left, bottom);
ctx.fill();
- if (labelOpt.lineWidth > 0) {
+ if (lineWidth > 0) {
ctx.stroke();
}
- ctx.fillStyle = labelOpt.fontColor;
- ctx.fillText(labelOpt.text, x, y);
+ ctx.fillStyle = fontColor;
+
+ ctx.fillText(label, textX, textY);
ctx.closePath();
}
}
diff --git a/src/components/chart/scale/scale.step.js b/src/components/chart/scale/scale.step.js
index 3dfdb0f8c..93bfa0f81 100644
--- a/src/components/chart/scale/scale.step.js
+++ b/src/components/chart/scale/scale.step.js
@@ -1,5 +1,5 @@
import { defaultsDeep } from 'lodash-es';
-import { PLOT_LINE_OPTION } from '@/components/chart/helpers/helpers.constant';
+import { PLOT_BAND_OPTION, PLOT_LINE_OPTION } from '@/components/chart/helpers/helpers.constant';
import Scale from './scale';
import Util from '../helpers/helpers.util';
@@ -160,15 +160,42 @@ class StepScale extends Scale {
ctx.closePath();
- // draw plot line
- if (this.plotLines?.length) {
+ // draw plot lines and plot bands
+ if (this.plotBands?.length || this.plotLines?.length) {
const padding = aliasPixel + 1;
const minX = aPos.x1 + padding;
const maxX = aPos.x2;
const minY = aPos.y1 + padding;
const maxY = aPos.y2;
- this.plotLines.forEach((plotLine) => {
+ this.plotBands?.forEach((plotBand) => {
+ if (!plotBand.from && !plotBand.to) {
+ return;
+ }
+
+ const mergedPlotBandOpt = defaultsDeep({}, plotBand, PLOT_BAND_OPTION);
+ const { from = 0, to = labels.length, label: labelOpt } = mergedPlotBandOpt;
+ const fromPos = Math.round(startPoint + (labelGap * from));
+ const toPos = Math.round(startPoint + (labelGap * to));
+
+ this.setPlotBandStyle(mergedPlotBandOpt);
+
+ if (this.type === 'x') {
+ this.drawXPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
+ } else {
+ this.drawYPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
+ }
+
+ if (labelOpt.show) {
+ const labelOptions = this.getNormalizedLabelOptions(chartRect, labelOpt);
+ const textXY = this.getPlotBandLabelPosition(fromPos, toPos, labelOptions, maxX, minY);
+ this.drawPlotLabel(labelOptions, textXY);
+ }
+
+ ctx.restore();
+ });
+
+ this.plotLines?.forEach((plotLine) => {
if (!plotLine.value) {
return;
}
@@ -180,9 +207,15 @@ class StepScale extends Scale {
this.setPlotLineStyle(mergedPlotLineOpt);
if (this.type === 'x') {
- this.drawXPlotLine(dataPos, minX, maxX, minY, maxY, labelOpt);
+ this.drawXPlotLine(dataPos, minX, maxX, minY, maxY);
} else {
- this.drawYPlotLine(dataPos, minX, maxX, minY, maxY, labelOpt);
+ this.drawYPlotLine(dataPos, minX, maxX, minY, maxY);
+ }
+
+ if (labelOpt.show) {
+ const labelOptions = this.getNormalizedLabelOptions(chartRect, labelOpt);
+ const textXY = this.getPlotLineLabelPosition(dataPos, labelOptions, maxX, minY);
+ this.drawPlotLabel(labelOptions, textXY);
}
ctx.restore();