The McCharts component library is developed based on Hongmeng ArkTS syntax and supports API 9 and above. The chart component has been open sourced. Everyone can participate, whether they are new or experienced, to learn from each other, develop more component libraries, and enrich the ArkTS ecosystem.
You can check out the article address. If you think it is good, remember to give a little star to the open source project. You can also join the exchange group to learn from each other. Back to the topic,
When implementing a common component, I will first analyze the approximate implementation structure and development ideas, so that we can avoid a little detour; it can also make the component easier to expand and more maintainable. However, I will not directly talk about all the encapsulated ones. I will break down the functions one by one. In this way, everyone can learn more. Below I will briefly list the functional structure of the line chart component:
· Public properties
-
Width and height
-
Spacing between top, bottom, left and right
-
Font size
-
Font color
-
Data
· Drawing coordinate axes
-
Drawing X-axis
-
Drawing axis
-
Drawing dividing lines
-
Drawing scale lines
-
Drawing text labels
-
Drawing Y-axis
-
Drawing axis
-
Drawing dividing lines
-
Drawing scale lines
-
Drawing text labels
· Drawing line area
-
Drawing line
-
Drawing punctuation and text labels
This is a functional structure I roughly drew, which can be said to be some relatively simple basic functions. In the future, there will be click triggers, animations, and other functions that will also be planned. In this issue, we will first implement the above basic functions, and then slowly expand them later.
A component will definitely have some public properties as dynamic parameters to facilitate information transmission between components. Let's explain the functions of the five public properties separately:
-
The width (cWidth) and height (cHeight) of the canvas, which are the most basic. But I control whether it is necessary to pass it here, and the default value is 100%.
-
The internal blank spacing of the canvas (cSpace). It is mainly used to control the distance between the content area and the outer frame of the canvas to prevent the content of the painting from being cut off.
-
Font size (fontSize). It is mainly used to control the font size of the entire painting content, globally, to avoid the need to pass the font size for each small function.
-
Font color (color). The function is the same as the font size.
-
Chart data (data). An array used to store the content of the chart, where name and value are required.
The following is the specific code:
//Chart data feature interface
interface interface_data {
name: string | number;
value: string | number;
[key: string]: any;
}
//Chart feature interface
interface interface_option {
cWidth?: string | number,
cHeight?: string | number,
fontSize?: string | number,
color?: string,
cSpace?: number,
data?: interface_data[]
}
//option default value
const def_option: interface_option = {
cWidth: '100%',
cHeight: '100%',
fontSize: 10,
color: '#333',
cSpace: 20,
data: []
}
@Component
export struct McLineChart {
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
@State options: interface_option = {}
aboutToAppear() {
this.options = Object.assign({}, def_option, this.options)
}
build() {
Canvas(this.context)
.width(this.options.cWidth)
.height(this.options.cHeight)
.onReady(() => {
})
}
}
After talking about some basic properties of the canvas, let's start drawing the chart content area. First, draw the coordinate axis. The coordinate axis is divided into the X axis and the Y axis. We should start drawing the Y axis first. Why? Because I have helped you step on the pit, the reason is: to display the text label on the y-axis, if the maximum width corresponding to the text label is not obtained at the beginning, then the starting coordinates of the y-axis and the x-axis will be deviated, which will cause the painting to be misplaced. Let me show you the effect of the complete coordinate axis first
The Y-axis as a whole is composed of the axis line, the dividing line, the scale line, and the text label. The four parts have a sequence relationship and contain certain algorithm logic. I can simply explain it with a concept map.
I used a 500*500 rectangle as our canvas this time. We can see from the figure that the Y-axis as a whole includes four parts: text label, Y-axis line, dividing line, and scale line. Those who know canvas know that canvas painting is basically positioned by coordinates, and the starting and ending coordinates of the four parts of our Y-axis as a whole are related to each other, and even need to calculate the four properties of internal spacing, dividing spacing, y-axis height, and maximum text width. I have been talking about concepts and ideas for so long, but I haven't started to talk about code yet. I'm really sorry. Next, we will explain them one by one:
- Calculate the maximum width of the text (maxNameW). We can see from the figure that the starting coordinates of the y-axis line, scale line, and dividing line all need to be calculated by adding the content spacing, text label, and the spacing between the text label and the dividing line (optional, depending on your needs). In order to maintain alignment, we need to calculate the maximum width of the text. The text on the y-axis is generally the value corresponding to the data (data), so we need to get the maximum value in the input data (data). Then divide the maximum value into five equal parts (here we have fixed it, and you can change it to dynamic later). The following is the code for calculating the maximum text width. I will also write some logic in the code. Please read the code carefully:
build() {
Canvas(this.context)
.width(this.options.cWidth)
.height(this.options.cHeight)
.backgroundColor(this.options.backgroundColor)
.onReady(() => {
const values: number[] = this.options.data.map((item) => Number(item.value || 0))
const maxValue = Math.max(...values)
let maxNameW = 0
let cSpiltNum = 5 // Split into equal parts
let cSpiltVal = maxValue / cSpiltNum // Calculate the split spacing
for(var i = 0; i <= this.options.data.length; i++){
// Divide the maximum value by the equal division to get the interval value of each text, and multiply the interval value by i each time to get the value corresponding to each scale. The calculation shows that the integer needs to be retained and converted into a string
const text = (cSpiltVal * i).toFixed(0)
const textWidth = this.context.measureText(text).width; // Get the length of the text
maxNameW = textWidth > maxNameW ? textWidth : maxNameW // Match the maximum value each time
}
})
}
- Draw the text label. We can see from the figure that the x-coordinate of the text label is only related to the internal spacing, and we have already obtained the segmentation spacing of each scale from the above code, so we can get the y-axis of each text.
.onReady(() => {
....
for(var i = 0; i <= this.options.data.length; i++){
...
// Draw text labels
this.context.fillText(text, this.options.cSpace, cSpiltVal * (this.options.data.length - i) + this.options.cSpace , 0);
}
})
- Draw tick marks. We can get the x-coordinate algorithm of the starting point of the tick mark from the concept map: the internal spacing (cSpace) plus the longest text width (maxNameW) plus the spacing between the text and the tick mark (I didn't draw this specifically, you can look at your own business), the starting point y-coordinate is the same as the text, and the y-coordinate of each scale is obtained by dividing the spacing and the subscript relationship; the end point x-coordinate is the length of the tick mark, and the end point y-coordinate is the same as the starting point y-coordinate. I set the default length to 5, so we can get our tick marks. The code is as follows:
.onReady(() => {
....
const length = this.options.data.length
for(var i = 0; i <= length; i++){
...
}
// The above is the code for getting the longest text width
// Line drawing method
function drawLine(x, y, X, Y){
this.context.beginPath();
this.context.moveTo(x, y);
this.context.lineTo(X, Y);
this.context.stroke();
this.context.closePath();
}
for(var i = 0; i <= length; i++){
const item = this.options.data[i]
// Drawing text label
ctx.fillText(text, this.options.cSpace, cSpiltVal * (this.data.length - i) + this.options.cSpace, 0);
// Internal spacing + text length
const scaleX = this.options.cSpace + maxNameW
// Calculate the equal intervals through the maximum value of the data, and then calculate the coordinates of each end point
const scaleY = cSpiltVal * (length - i) + this.options.cSpace
// The 5 here is the interval between the text and the scale line and the length of the scale line
drawLine(scaleX, scaleY, scaleX + 5 + 5, scaleY);
}
})
- Draw the y axis. Next is our y axis, and continue to analyze the overview diagram. From the figure, we can get: the algorithm of the starting point x coordinate of the y axis is: the internal spacing (cSpace) plus the longest text width (maxNameW) plus the spacing between the text and the scale line and the length of the scale line, and the starting point y coordinate is the internal upper spacing; and the end point x coordinate is the same as the starting point x coordinate, and the end point y coordinate algorithm is: canvas height minus the internal spacing on the upper and lower sides. Through the above calculation relationship, we can draw y