Entwicklungsfall: Verwenden Sie Canvas, um Liniendiagramme von Diagrammreihen zu implementieren

1. Funktionsstruktur

Analysieren Sie bei der Implementierung einer öffentlichen Komponente zunächst die allgemeine Implementierungsstruktur und die Entwicklungsideen, damit wir Umwege vermeiden und die Komponente einfacher erweiterbar und wartbarer machen können. Dann werde ich die Funktionen einzeln aufschlüsseln, damit jeder detailliertere Inhalte lernen kann. Im Folgenden finden Sie eine kurze Erläuterung des funktionalen Aufbaus der Liniendiagrammkomponente:

Das Obige ist das grundlegende Funktionsstrukturgerüst, das einige relativ einfache Grundfunktionen umfasst. In Zukunft sind auch Klickauslösung, Animation und andere Funktionen geplant. In dieser Ausgabe werden wir zunächst die oben genannten Grundfunktionen implementieren und diese dann in Zukunft langsam erweitern.

2. Öffentliche Attribute

1. Eine Komponente verfügt auf jeden Fall über einige öffentliche Eigenschaften als dynamische Parameter, um die Informationsübertragung zwischen Komponenten zu erleichtern. Lassen Sie uns die Funktionen der fünf öffentlichen Eigenschaften erklären: die Breite (cWidth) und die Höhe (cHeight) der Leinwand. Dies ist die grundlegendste von. Aber hier kontrolliere ich, ob es übergeben werden muss oder nicht, und der Standardwert ist 100 %.

2. Der interne Leerraum der Leinwand (cSpace). Es wird hauptsächlich verwendet, um den Abstand zwischen dem Inhaltsbereich und dem Leinwandrahmen zu steuern, um zu verhindern, dass der Bildinhalt abgeschnitten wird.

3. Schriftgröße (fontSize). Hauptsächlich, um die Schriftgröße des gesamten Malinhalts global zu steuern, um zu vermeiden, dass die Schriftgröße für jede kleine Funktion übergeben werden muss.

4. Schriftfarbe (Farbe). Funktionell konsistent mit der Schriftgröße.

5. Kartendaten. Array zum Speichern von Diagramminhalten, wobei Name und Wert erforderlich sind.

Das Folgende ist der spezifische Code:

 // 图表数据的特征接口
interface interface_data {
  name: string | number;
  value: string | number;
  [key: string]: any;
}


// 图表的特征接口
interface interface_option {
  cWidth?: string | number,
  cHeight?: string | number,
  fontSize?: string | number,
  color?: string,
  cSpace?: number,
  data?: interface_data[]
}


// option 默认值
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(() => {
        
      })
  }
}

3. Malen der Koordinatenachsen

Im Inhaltsbereich des Zeichendiagramms muss zunächst die Koordinatenachse gezeichnet werden. Die Koordinatenachse ist unterteilt in: Wenn die Breite den Maximalwert hat, dann sind die Startpunktkoordinaten der Y-Achse und die X-Achse. Die Achse weicht ab, was zu einer Fehlausrichtung des gesamten Gemäldes führt. Das Bild unten zeigt den Effekt der vollständigen Koordinatenachse.

1. Y-Achse zeichnen

Die Y-Achse als Ganzes besteht aus vier Teilen: Achse, Trennlinie, Häkchen und Textbeschriftung. Die vier Teile stehen in einer sequentiellen Beziehung und enthalten eine bestimmte Algorithmuslogik. Lassen Sie uns dies kurz anhand eines Konzeptdiagramms erklären.

Verwenden Sie dieses Mal zunächst ein 500*500-Rechteck als Leinwand. Im Bild können wir sehen, dass die Y-Achse als Ganzes aus vier Teilen besteht: Textbeschriftung, Y-Achsenlinie, Trennlinie und Häkchen. Leinwandgemälde werden im Wesentlichen anhand von Koordinaten positioniert. Die Startpunkt- und Endkoordinaten der vier Teile der Y-Achse hängen alle miteinander zusammen. Es müssen sogar die vier Attribute Innenabstand, Teilungsabstand, Höhe der Y-Achse berechnet werden. und maximale Textbreite. Innen. Das Obige sind die Konzepte und Ideen. Als Nächstes erklären wir den Code nacheinander:

1. Berechnen Sie die längste Breite des Textes (maxNameW). Aus der Abbildung können wir ersehen, ob es sich um die y-Achse, das Häkchen oder die Startpunktkoordinaten der Trennlinie, den Inhaltsabstand, die Textbeschriftung oder die Textbeschriftung handelt und Trennlinienintervall müssen addiert werden. Berechnet, und um die Ausrichtung beizubehalten, müssen wir die maximale Breite des Textes berechnen. Der Text auf der y-Achse ist im Allgemeinen der Wert, der den Daten (Daten) entspricht. Daher müssen wir den Maximalwert in den eingehenden Daten (Daten) ermitteln. Dann wird der Maximalwert in fünf gleiche Teile geteilt. Das Folgende ist der Code zum Berechnen und Erhalten der maximalen Textbreite. Ich werde auch einen Teil der Logik in den Code schreiben:

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 // 分割等分
        let cSpiltVal = maxValue / cSpiltNum // 计算分割间距
        for(var i = 0; i <= this.options.data.length; i++){
          // 用最大值除于分割等分得到每一个文本的间隔值,而每一次遍历用间隔值乘于i就能得到每个刻度对应的数值了,计算得到得知需要保留整数且转成字符串
          const text = (cSpiltVal * i).toFixed(0)
          const textWidth = this.context.measureText(text).width; // 获取文字的长度
          maxNameW = textWidth > maxNameW ? textWidth : maxNameW // 每次进行最大值的匹配
        }
      })
}

2. Zeichnen Sie die Textbeschriftung. Aus der Abbildung können wir ersehen, dass die x-Koordinate der Textbeschriftung nur mit dem Innenabstand zusammenhängt, und wir haben den Teilungsabstand jeder Skala aus dem obigen Code erhalten, sodass wir ihn erhalten können die y-Achse jedes Textes.

.onReady(() => {
   ....
   for(var i = 0; i <= this.options.data.length; i++){
     ...
     // 绘画文本标签
     this.context.fillText(text, this.options.cSpace, cSpiltVal * (this.options.data.length - i) + this.options.cSpace , 0);
   }
})

3. Zeichnen Sie die Markierungen. Der Konzeptkarte können wir entnehmen, dass der x-Koordinatenalgorithmus des Startpunkts des Teilstrichs wie folgt lautet: Innenabstand (cSpace) plus längste Textbreite (maxNameW) plus Abstand zwischen Text und Teilstrich. Der y- Die Koordinate des Startpunkts ist dieselbe wie die des Textes. Durch Teilen des Abstands und der Beziehung zwischen den Indizes erhält man die y-Koordinate jeder Skala; die x-Koordinate des Endpunkts ist die Länge des Teilstrichs und die y-Koordinate des Endpunkts ist die Identisch mit der Y-Koordinate des Startpunkts. Ich habe die Standardlänge auf 5 gesetzt, damit wir unsere Teilstriche erhalten können. Code wie folgt anzeigen:

.onReady(() => {
  ....
  const length = this.options.data.length
  for(var i = 0; i <= length; i++){
    ...
  }
  // 上面是获取最长文本宽度的代码
  // 画线的方法
  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]
    // 绘画文本标签
    ctx.fillText(text, this.options.cSpace,  cSpiltVal * (this.data.length - i) + this.options.cSpace, 0);
    // 内部间距+文本长度
    const scaleX = this.options.cSpace + maxNameW
    // 通过数据最大值算出等分间隔,从而计算出每一个的终点坐标
    const scaleY = cSpiltVal * (length - i) + this.options.cSpace
    // 这里的5就是我设置文本跟刻度线的间隔与刻度线的长度
    drawLine(scaleX, scaleY, scaleX + 5 + 5, scaleY);
  }
})

4. Zeichnen Sie die y-Achse. Wenn wir das Übersichtsdiagramm weiter analysieren, können wir aus dem Diagramm Folgendes erhalten: Der Algorithmus der x-Koordinate des Startpunkts der y-Achse lautet: Innenabstand (cSpace) plus die längste Textbreite (maxNameW) plus der Abstand zwischen dem Text und Das Häkchen und die Länge des Häkchens, der Startpunkt y Die Koordinate ist der interne obere Abstand, die Endpunkt-X-Koordinate ist dieselbe wie die Startpunkt-X-Koordinate und der Endpunkt-Y-Koordinatenalgorithmus ist: die Höhe von der Leinwand abzüglich des Innenabstands zwischen Ober- und Unterseite. Durch die obige Berechnungsbeziehung kann die y-Achse gezeichnet werden. Code wie folgt anzeigen:

.onReady(() => {
   
     ...  // 上面是绘画其他组成部分代码   const startX = this.options.cSpace + maxNameW + 5 + 5   const startY = this.options.cSpace   const endX = startX   const endY = this.context.height - (this.options.cSpace * 2)   drawLine(startX, startY, endX, endY); // 绘画y轴})

5. Trennlinien zeichnen. Tatsächlich ist aus der Abbildung ersichtlich, dass die Trennlinie der Skalenmarkierung ähnelt. Der Algorithmus für die x-Koordinaten des Startpunkts lautet: Addieren Sie die Länge der Skalenlinie zur x-Koordinate des Startpunkts der Skalenmarkierung und zum Startpunkt der y-Achse ist mit der Skalenmarkierung identisch. Der X-Koordinatenalgorithmus des Endpunkts ist: die Breite der Leinwand minus die X-Koordinate des Startpunkts; die Y-Koordinate des Endpunkts ist dieselbe wie die Y-Koordinate des Startpunkts. Der spezifische Code lautet wie folgt:

.onReady(() => {
  ....
  // 上面是获取最长文本宽度的代码
  for(var i = 0; i <= length; i++){
    const item = this.options.data[i]
    // 绘画文本标签跟刻度
    ...
    // 绘画分割线
    const splitX = scaleX + 5 + 5
    const splitY = scaleY
    drawLine(splitX, splitY, this.context.width - splitX - this.options.cSpace, splitY);
  }
})

2. Zeichnen Sie die X-Achse

Nachdem wir die Y-Achse gezeichnet haben, zeichnen wir dann die X-Achse. Die Zeichenlogik der X-Achse und der Y-Achse ist dieselbe, jedoch in unterschiedliche Richtungen. Die spezifischen Algorithmen werden nicht einzeln erläutert. Sie können sich auf das Konzeptdiagramm beziehen.

Was mit der Y-Achse des Gemäldes nicht übereinstimmt, ist:

1. Die längsten Objekte sind unterschiedlich. Die längste Y-Achse ist die Textbreite und die längste X-Achse, die ermittelt werden muss, ist die Texthöhe.

2. Die Anzahl der Intervallunterteilungen ist unterschiedlich. Die Y-Achse ist die benutzerdefinierte Teilungszahl und die Teilungslinie der X-Achse ist die Länge der tatsächlichen Daten.

3. Der Algorithmus der Segmentierungsentfernungslänge ist unterschiedlich. Der Y-Achsen-Algorithmus verwendet den Maximalwert der Daten bei einer benutzerdefinierten Teilungszahl; während der X-Achsen-Algorithmus die Leinwandbreite zum Subtrahieren verwendet (die internen Lücken auf der linken und rechten Seite und die Y-Achsenbreite (die längste Breite). des Textes plus die Tick-Breite)) und entfernt dann die Länge der Daten, um die Länge jedes Intervalls zu erhalten.

Zusätzlich zu den oben genannten drei Punkten, die beachtet werden müssen, besteht die andere Sache darin, die Berechnungsposition zu ändern. Der Gesamtcode für die X-Achse lautet wie folgt:

.onReady(() => {
  const cSpace = this.options.cSpace
  // 上面是绘制y轴的代码
  ....
  // 绘制x轴
  // 获取每个分割线的间距:this.context.width - 20为x轴的长度
  let xSplitSpacing = parseInt(String((this.context.width - cSpace * 2 - maxNameW) / this.options.data.length))
  let x = 0;
  for(var i = 0; i <= this.options.data.length; i++){
    // 绘画分割线
    x = xSplitSpacing * (i + 1) // 计算每个数值的x坐标值
    this.drawLine(x + cSpace + maxNameW, this.context.height - cSpace, x + cSpace + maxNameW, cSpace);
    // 绘制刻度
    this.drawLine(x + cSpace + maxNameW, this.context.height - cSpace, x + cSpace + maxNameW, this.context.height - cSpace);
    // 绘制文字刻度标签
    const text = this.options.data[i].name
    const textWidth = this.context.measureText(text).width; // 获取文字的长度
    // 这里文本的x坐标需要减去本身文本宽度的一半,这样才能居中显示, y坐标这是画布高度减去内部间距即可
    this.context.fillText(text, x + cSpace + maxNameW - textWidth / 2, this.context.height - cSpace, 0);
  }
this.context.save();
  this.context.rotate(-Math.PI/2);
  this.context.restore();
})

4. Malen Sie den Polylinienbereich

Nachdem Sie die Koordinatenachse gezeichnet haben, können Sie den Inhalt des Polylinienbereichs zeichnen. Es steht auch im Mittelpunkt der gesamten Leinwand. Der Polylinienbereich ist in drei Teile unterteilt: Polylinienzeichnung, Interpunktionszeichnung und Textzeichnung.

1. Zeichnen Sie Polylinien

Aus dem Bild oben können wir ersehen, dass die Polylinie die tatsächlichen Datenwerte direkt in x- und y-Koordinaten umwandelt und sie dann durch Linien verbindet. Der X-Koordinatenalgorithmus jedes Wendepunkts entspricht der Skala oder dem Text der X-Achse, und die Y-Koordinate ist der tatsächliche Wert, der durch einen bestimmten Algorithmus in die Höhe umgewandelt wird, die wir benötigen. Wir haben die x-Koordinate bereits erhalten, wir müssen nur noch unsere y-Koordinate ermitteln. Sie können die Beziehung zwischen der Leinwand und den tatsächlichen Daten anhand des Diagramms beobachten:

Zunächst stellt die Höhe der Y-Achse den Maximalwert der tatsächlichen Daten dar. Dies ist das Ergebnis, das wir erhalten, wenn wir die Y-Achse zeichnen. Anschließend können wir den Skalierungsfaktor (Skalierung) der Höhe der Y-Achse berechnen und die tatsächlichen Daten und jede der Polylinien. Die y-Koordinate entspricht auch einem tatsächlichen Wert. Sie müssen den tatsächlichen Wert in die Höhe der Leinwand umrechnen. Multiplizieren Sie dann den tatsächlichen Wert mit der Skala, die Sie gerade erhalten haben, um die umgewandelte Höhe zu erhalten .

Obwohl wir die skalierte Höhe jedes Wendepunkts erhalten haben, müssen wir die untere Innenhöhe plus die Höhe der Höhe der Leinwand und subtrahieren Sie dann die skalierte tatsächliche Höhe. Was auf diese Weise berechnet wird, ist der y-Koordinatenwert, den wir wollen. Wir kennen wahrscheinlich bereits die Algorithmusbeziehung. Das Folgende ist der endgültige Code:

.onReady(() => {
  ...
  // 上面是绘制x轴跟y轴的代码
  // 绘画折线
  const ySacle = (this.context.height - cSpace *2) / maxValue // 计算出y轴与实际最大值的缩放倍数
  //连线
  this.context.beginPath();
  for(var i=0; i< this.options.data.length; i++){
    const dotVal = String(this.options.data[i].value);
    const x = xSplitSpacing * (i + 1) + cSpace + maxNameW // 计算每个数值的x坐标值
    const y = this.context.height - cSpace - parseInt(dotVal * ySacle); // 画布的高度减去下边内部高度加x轴高度,再减去缩放后的实际高度
    if(i==0){
      // 第一个作为起点
      this.context.moveTo( x, y );
    }else{
      this.context.lineTo( x, y );
    }
  }
  ctx.stroke();
})

2. Malen von Satzzeichen und Textbeschriftungen

Nachdem wir die Polylinie gezeichnet haben, können wir im Grunde viele Dinge erhalten, wie zum Beispiel die x- und y-Koordinaten jedes Wendepunkts auf der Polylinie. Dies ist für uns sehr praktisch, um Satzzeichen und Textbeschriftungen zu zeichnen:

.onReady(() => {
  ...
  // 上面是绘制x轴跟y轴的代码
  // 绘画折线
  const ySacle = (this.context.height - cSpace *2) / maxValue // 计算出y轴与实际最大值的缩放倍数
  this.context.beginPath();
  for(var i=0; i< this.options.data.length; i++){
    // 绘画折线代码
    ...
    // 绘制标点
    drawArc(x, y);
    // 绘制文本标签
    const textWidth = this.context.measureText(dotVal).width; // 获取文字的长度
    const textHeight = this.context.measureText(dotVal).height; // 获取文字的长度
    this.context.fillText(dotVal, x - textWidth / 2, y - textHeight / 2); // 文字
  }


  function drawArc( x, y ){
    this.context.beginPath();
    this.context.arc( x, y, 3, 0, Math.PI*2 );
    this.context.fill();
    this.context.closePath();
  }
  this.context.stroke();
})

Der Endeffekt ist wie folgt:

5. Zusammenfassung

Das Obige ist diese technische Analyse, ich hoffe, sie kann alle inspirieren, und ich wünsche mir auch, dass alle Entwickler den idealen Effekt entwickeln können. In Zukunft werden wir die diagrammbezogenen Komponentenreihen in einer Komponentenbibliothek kapseln und auf den Markt bringen , so dass sie direkt ausgepackt werden können. Sofort einsatzbereit. Bitte bleiben Sie dran, es wird in Zukunft viele technische Austausche geben, verpassen Sie es nicht!

Supongo que te gusta

Origin blog.csdn.net/HarmonyOSDev/article/details/134978188
Recomendado
Clasificación