1/*
2 * Copyright (c) 2020-2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "components/ui_chart.h"
17#include "engines/gfx/gfx_engine_manager.h"
18#include "securec.h"
19
20namespace OHOS {
21UIChart::~UIChart()
22{
23    if (mixData_ != nullptr) {
24        UIFree(mixData_);
25        mixData_ = nullptr;
26    }
27    ClearDataSerial();
28    Remove(&xAxis_);
29    Remove(&yAxis_);
30}
31
32void UIChart::SetHeight(int16_t height)
33{
34    if (GetHeight() == height) {
35        return;
36    }
37
38    if (height > 0) {
39        needRefresh_ = true;
40    }
41
42    UIView::SetHeight(height);
43    xAxis_.SetHeight(height);
44    xAxis_.UpdateAxis();
45    yAxis_.SetHeight(height);
46    yAxis_.UpdateAxis();
47}
48
49void UIChart::SetWidth(int16_t width)
50{
51    UIView::SetWidth(width);
52    xAxis_.SetWidth(width);
53    yAxis_.SetWidth(width);
54    xAxis_.UpdateAxis();
55    yAxis_.UpdateAxis();
56}
57
58void UIChart::OnDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
59{
60    UIViewGroup::OnDraw(gfxDstBuffer, invalidatedArea);
61    DrawDataSerials(gfxDstBuffer, invalidatedArea);
62}
63
64bool UIChart::AddDataSerial(UIChartDataSerial* dataSerial)
65{
66    if (dataSerial == nullptr) {
67        return false;
68    }
69
70    ListNode<UIChartDataSerial*>* serialNode = list_.Head();
71    while (serialNode != list_.End()) {
72        if (serialNode->data_ == dataSerial) {
73            return false;
74        }
75        serialNode = serialNode->next_;
76    }
77    list_.PushBack(dataSerial);
78    dataSerial->BindToChart(this);
79    return true;
80}
81
82bool UIChart::DeleteDataSerial(UIChartDataSerial* dataSerial)
83{
84    if ((dataSerial == nullptr) || list_.IsEmpty()) {
85        return false;
86    }
87
88    bool findSerial = false;
89    ListNode<UIChartDataSerial*>* serialNode = list_.Head();
90    while (serialNode != list_.End()) {
91        if (serialNode->data_ == dataSerial) {
92            dataSerial->BindToChart(nullptr);
93            list_.Remove(serialNode);
94            findSerial = true;
95            break;
96        }
97        serialNode = serialNode->next_;
98    }
99
100    return findSerial;
101}
102
103void UIChart::ClearDataSerial()
104{
105    if (list_.IsEmpty()) {
106        return;
107    }
108
109    ListNode<UIChartDataSerial*>* serialNode = list_.Head();
110    while (serialNode != list_.End()) {
111        serialNode->data_->BindToChart(nullptr);
112        ListNode<UIChartDataSerial*>* tempNode = serialNode;
113        serialNode = serialNode->next_;
114        list_.Remove(tempNode);
115    }
116    list_.Clear();
117}
118
119UIChartDataSerial::UIChartDataSerial()
120    : maxCount_(0),
121      pointArray_(nullptr),
122      serialColor_(Color::White()),
123      fillColor_(Color::White()),
124      dataCount_(0),
125      peakPointIndex_(0),
126      peakData_(0),
127      valleyData_(0),
128      valleyPointIndex_(0),
129      lastPointIndex_(0),
130      latestIndex_(0),
131      hideIndex_(0),
132      hideCount_(0),
133      smooth_(false),
134      enableGradient_(false),
135      enableHeadPoint_(false),
136      enableTopPoint_(false),
137      enableBottomPoint_(false),
138      chart_(nullptr),
139      invalidateRect_(0, 0, 0, 0)
140{
141    PointStyle style;
142    style.radius = DEFAULT_POINT_RADIUS;
143    style.strokeWidth = 1;
144    style.fillColor = Color::White();
145    style.strokeColor = Color::White();
146    topPointStyle_ = style;
147    bottomPointStyle_ = style;
148    headPointStyle_ = style;
149}
150
151bool UIChartDataSerial::SetMaxDataCount(uint16_t maxCount)
152{
153    if (maxCount > MAX_POINTS_COUNT) {
154        maxCount = MAX_POINTS_COUNT;
155    }
156
157    if (maxCount == maxCount_) {
158        return true;
159    }
160
161    if (pointArray_ != nullptr) {
162        UIFree(pointArray_);
163        pointArray_ = nullptr;
164    }
165
166    maxCount_ = maxCount;
167    if (maxCount_ == 0) {
168        return true;
169    }
170
171    pointArray_ = static_cast<Point*>(UIMalloc(sizeof(Point) * maxCount_));
172    if (pointArray_ == nullptr) {
173        maxCount_ = 0;
174        return false;
175    }
176    return true;
177}
178
179bool UIChartDataSerial::ModifyPoint(uint16_t index, const Point& point)
180{
181    if ((index >= maxCount_) || (pointArray_ == nullptr)) {
182        return false;
183    }
184
185    pointArray_[index].x = point.x;
186    pointArray_[index].y = point.y;
187    if (point.y > peakData_) {
188        if (enableTopPoint_) {
189            RefreshInvalidateRect(peakPointIndex_, topPointStyle_);
190        }
191        peakPointIndex_ = index;
192        peakData_ = point.y;
193    } else if (point.y < valleyData_) {
194        if (enableBottomPoint_) {
195            RefreshInvalidateRect(valleyPointIndex_, bottomPointStyle_);
196        }
197        valleyPointIndex_ = index;
198        valleyData_ = point.y;
199    } else if ((index == peakPointIndex_) || (index == valleyPointIndex_)) {
200        UpdatePeakAndValley(0, dataCount_);
201    }
202
203    latestIndex_ = index;
204    uint16_t startIndex = (index == 0) ? index : (index - 1);
205    RefreshInvalidateRect(startIndex, index + 1);
206    return true;
207}
208
209bool UIChartDataSerial::GetPoint(uint16_t index, Point& point)
210{
211    if ((index >= dataCount_) || (pointArray_ == nullptr)) {
212        return false;
213    }
214    point = pointArray_[index];
215    if (chart_ != nullptr) {
216        chart_->GetXAxis().TranslateToPixel(point.x);
217        chart_->GetYAxis().TranslateToPixel(point.y);
218    }
219    return true;
220}
221
222bool UIChartDataSerial::GetOriginalPoint(uint16_t index, Point& point)
223{
224    if ((index >= dataCount_) || (pointArray_ == nullptr)) {
225        return false;
226    }
227    point = pointArray_[index];
228    return true;
229}
230
231bool UIChartDataSerial::PointArrayDup(Point** pointArrayBack)
232{
233    *pointArrayBack = pointArray_;
234    pointArray_ = static_cast<Point*>(UIMalloc(sizeof(Point) * maxCount_));
235    return memcpy_s(pointArray_, dataCount_ * sizeof(Point), *pointArrayBack, dataCount_ * sizeof(Point)) != EOK;
236}
237
238void UIChartDataSerial::HidePoint(uint16_t index, uint16_t count)
239{
240    hideIndex_ = index;
241    hideCount_ = count;
242    RefreshInvalidateRect(hideIndex_, hideIndex_ + hideCount_);
243}
244
245void UIChartDataSerial::RefreshInvalidateRect(uint16_t pointIndex, const PointStyle& style)
246{
247    Point point;
248    if (GetPoint(pointIndex, point)) {
249        uint16_t width = style.radius + style.strokeWidth;
250        Rect refresh(point.x - width, 0, point.x + width, 0);
251        if ((invalidateRect_.GetLeft() == 0) && (invalidateRect_.GetRight() == 0)) {
252            invalidateRect_ = refresh;
253        } else {
254            invalidateRect_.Join(invalidateRect_, refresh);
255        }
256    }
257}
258
259void UIChartDataSerial::RefreshInvalidateRect(uint16_t startIndex, uint16_t endIndex)
260{
261    Point start;
262    GetPoint(startIndex, start);
263    Point end;
264    endIndex = (endIndex >= dataCount_) ? (dataCount_ - 1) : endIndex;
265    GetPoint(endIndex, end);
266    int16_t xMin = MATH_MIN(start.x, end.x);
267    int16_t xMax = MATH_MAX(start.x, end.x);
268    Rect refresh(xMin, 0, xMax, 0);
269    if ((invalidateRect_.GetLeft() == 0) && (invalidateRect_.GetRight() == 0)) {
270        invalidateRect_ = refresh;
271        return;
272    }
273    invalidateRect_.Join(invalidateRect_, refresh);
274}
275
276bool UIChartDataSerial::UpdatePeakAndValley(uint16_t startPos, uint16_t endPos)
277{
278    if ((startPos >= endPos) || (endPos > dataCount_) || (pointArray_ == nullptr)) {
279        return false;
280    }
281
282    if (startPos == 0) {
283        peakData_ = pointArray_[startPos].y;
284        valleyData_ = pointArray_[startPos].y;
285    }
286
287    for (uint16_t i = startPos; i < endPos; i++) {
288        if (pointArray_[i].y > peakData_) {
289            if (enableTopPoint_) {
290                RefreshInvalidateRect(peakPointIndex_, topPointStyle_);
291                RefreshInvalidateRect(i, topPointStyle_);
292            }
293            peakPointIndex_ = i;
294            peakData_ = pointArray_[i].y;
295        }
296
297        if (pointArray_[i].y < valleyData_) {
298            if (enableBottomPoint_) {
299                RefreshInvalidateRect(valleyPointIndex_, bottomPointStyle_);
300                RefreshInvalidateRect(i, bottomPointStyle_);
301            }
302            valleyPointIndex_ = i;
303            valleyData_ = pointArray_[i].y;
304        }
305    }
306    return true;
307}
308
309bool UIChartDataSerial::AddPoints(const Point* data, uint16_t count)
310{
311    if ((maxCount_ <= dataCount_) || (count == 0) || (pointArray_ == nullptr) || (data == nullptr)) {
312        return false;
313    }
314
315    if (count > (maxCount_ - dataCount_)) {
316        count = maxCount_ - dataCount_;
317    }
318
319    Point* current = pointArray_ + dataCount_;
320    if (memcpy_s(current, (maxCount_ - dataCount_) * sizeof(Point), data, count * sizeof(Point)) != EOK) {
321        return false;
322    }
323    uint16_t i = dataCount_;
324    dataCount_ += count;
325    UpdatePeakAndValley(i, dataCount_);
326    latestIndex_ = dataCount_ - 1;
327    uint16_t startIndex = (i == 0) ? i : (i - 1);
328    RefreshInvalidateRect(startIndex, latestIndex_);
329    return true;
330}
331
332void UIChartDataSerial::ClearData()
333{
334    RefreshInvalidateRect(0, dataCount_ - 1);
335    if (pointArray_ != nullptr) {
336        if (memset_s(pointArray_, maxCount_ * sizeof(Point), 0, maxCount_ * sizeof(Point)) != EOK) {
337            return;
338        }
339    }
340    dataCount_ = 0;
341    valleyPointIndex_ = 0;
342    peakPointIndex_ = 0;
343    latestIndex_ = 0;
344}
345
346void UIChartDataSerial::DoDrawPoint(BufferInfo& gfxDstBuffer,
347                                    const Point& center,
348                                    const PointStyle& style,
349                                    const Rect& mask)
350{
351    Style drawStyle = StyleDefault::GetDefaultStyle();
352    drawStyle.lineOpa_ = OPA_OPAQUE;
353    drawStyle.lineColor_ = style.fillColor;
354
355    ArcInfo arcinfo = {{0}};
356    arcinfo.center = center;
357    arcinfo.imgPos = Point{0, 0};
358    arcinfo.radius = style.radius + style.strokeWidth;
359    arcinfo.startAngle = 0;
360    arcinfo.endAngle = CIRCLE_IN_DEGREE;
361    BaseGfxEngine* baseGfxEngine = BaseGfxEngine::GetInstance();
362    if (style.fillColor.full == style.strokeColor.full) {
363        drawStyle.lineWidth_ = style.radius + style.strokeWidth;
364        baseGfxEngine->DrawArc(gfxDstBuffer, arcinfo, mask, drawStyle, OPA_OPAQUE, CapType::CAP_NONE);
365        return;
366    }
367    drawStyle.lineWidth_ = style.radius;
368    arcinfo.radius = style.radius;
369    baseGfxEngine->DrawArc(gfxDstBuffer, arcinfo, mask, drawStyle, OPA_OPAQUE, CapType::CAP_NONE);
370
371    drawStyle.lineWidth_ = style.strokeWidth;
372    drawStyle.lineColor_ = style.strokeColor;
373    arcinfo.radius = style.radius + style.strokeWidth;
374    baseGfxEngine->DrawArc(gfxDstBuffer, arcinfo, mask, drawStyle, OPA_OPAQUE, CapType::CAP_NONE);
375}
376
377void UIChartDataSerial::DrawPoint(BufferInfo& gfxDstBuffer, const Rect& mask)
378{
379    Point center;
380    if (enableTopPoint_) {
381        if (GetPoint(peakPointIndex_, center)) {
382            DoDrawPoint(gfxDstBuffer, center, topPointStyle_, mask);
383        }
384    }
385
386    if (enableBottomPoint_) {
387        if (GetPoint(valleyPointIndex_, center)) {
388            DoDrawPoint(gfxDstBuffer, center, bottomPointStyle_, mask);
389        }
390    }
391
392    if (enableHeadPoint_) {
393        if (GetPoint(latestIndex_, center)) {
394            DoDrawPoint(gfxDstBuffer, center, headPointStyle_, mask);
395            lastPointIndex_ = latestIndex_;
396        }
397    }
398}
399
400void UIChartDataSerial::Refresh()
401{
402    if (chart_ != nullptr) {
403        Rect refresh = chart_->GetContentRect();
404        refresh.SetLeft(invalidateRect_.GetLeft() - headPointStyle_.radius - headPointStyle_.strokeWidth);
405        refresh.SetRight(invalidateRect_.GetRight() + headPointStyle_.radius + headPointStyle_.strokeWidth);
406        invalidateRect_.SetRect(0, 0, 0, 0);
407        chart_->InvalidateRect(refresh);
408
409        if (enableHeadPoint_ && (lastPointIndex_ != latestIndex_)) {
410            RefreshInvalidateRect(lastPointIndex_, headPointStyle_);
411            refresh.SetLeft(invalidateRect_.GetLeft());
412            refresh.SetRight(invalidateRect_.GetRight());
413            chart_->InvalidateRect(refresh);
414            invalidateRect_.SetRect(0, 0, 0, 0);
415        }
416    }
417}
418
419void UIChartPillar::RefreshChart()
420{
421    ListNode<UIChartDataSerial*>* iter = list_.Begin();
422    Rect rect = GetContentRect();
423    for (; iter != list_.End(); iter = iter->next_) {
424        UIChartDataSerial* data = iter->data_;
425        if (data == nullptr) {
426            break;
427        }
428        uint16_t dataCount = data->GetDataCount();
429        if (dataCount <= 1) {
430            break;
431        }
432
433        uint16_t index = data->GetLastPointIndex();
434        if (index >= dataCount) {
435            break;
436        }
437
438        Point current;
439        data->GetPoint(index, current);
440        Point last;
441        data->GetPoint(dataCount - 1, last);
442        Rect refresh(current.x, rect.GetTop(), last.x, rect.GetBottom());
443        InvalidateRect(refresh);
444        data->SetLastPointIndex(dataCount - 1);
445    }
446}
447
448void UIChartPillar::DrawDataSerials(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
449{
450    xAxis_.UpdateAxisPoints();
451    yAxis_.UpdateAxisPoints();
452    uint16_t minXStep = static_cast<uint16_t>(xAxis_.GetMarkInterval());
453    Point xStart = xAxis_.GetStartPoint();
454    uint16_t dataSerialCount = list_.Size();
455    if (dataSerialCount == 0) {
456        return;
457    }
458    uint16_t width = minXStep / dataSerialCount;
459    uint8_t dataSerialIndex = 0;
460    uint16_t barWidth = static_cast<uint16_t>(width - DEFAULT_MARK_PERCENTAGE * (width << 1));
461
462    for (ListNode<UIChartDataSerial*>* iter = list_.Begin(); iter != list_.End(); iter = iter->next_) {
463        UIChartDataSerial* data = iter->data_;
464        uint16_t dataSerialWidth = width * dataSerialIndex;
465        int16_t x = dataSerialWidth + (width >> 1);
466        for (uint16_t index = 0; index < data->GetDataCount(); index++) {
467            Point current;
468            data->GetPoint(index, current);
469            if (current.y == xStart.y) {
470                continue;
471            }
472            current.x += x;
473            xStart.x = current.x;
474            BaseGfxEngine::GetInstance()->DrawLine(gfxDstBuffer, current, xStart, invalidatedArea, barWidth,
475                                                   data->GetFillColor(), style_->lineOpa_);
476        }
477        dataSerialIndex++;
478    }
479}
480
481void UIChartPolyline::RefreshChart()
482{
483    ListNode<UIChartDataSerial*>* iter = list_.Begin();
484    for (; iter != list_.End(); iter = iter->next_) {
485        UIChartDataSerial* data = iter->data_;
486        uint16_t dataCount = data->GetDataCount();
487        if (dataCount == 1) {
488            break;
489        }
490        data->Refresh();
491    }
492}
493
494void UIChartPolyline::ReMeasure()
495{
496    if (!needRefresh_) {
497        return;
498    }
499    needRefresh_ = false;
500    int16_t height = GetHeight();
501    if (mixData_ != nullptr) {
502        UIFree(mixData_);
503        mixData_ = nullptr;
504    }
505    if (height <= 0) {
506        return;
507    }
508    if (height > COORD_MAX) {
509        height = COORD_MAX;
510    }
511    mixData_ = static_cast<uint8_t*>(UIMalloc(height));
512    if (mixData_ == nullptr) {
513        return;
514    }
515    int16_t opa = maxOpa_ - minOpa_;
516    for (int16_t y = 0; y < height; y++) {
517        mixData_[y] = static_cast<uint8_t>(y * opa / height + minOpa_);
518    }
519}
520
521void UIChartPolyline::DrawDataSerials(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
522{
523    xAxis_.UpdateAxisPoints();
524    yAxis_.UpdateAxisPoints();
525    ListNode<UIChartDataSerial*>* iter = list_.Begin();
526    for (; iter != list_.End(); iter = iter->next_) {
527        UIChartDataSerial* data = iter->data_;
528        uint16_t dataCount = data->GetDataCount();
529        if (dataCount <= 1) {
530            continue;
531        }
532        if (data->IsGradient()) {
533            if (data->IsSmooth()) {
534                Point* pointArrayBack = nullptr;
535                data->PointArrayDup(&pointArrayBack);
536                GetDataBySmooth(0, dataCount - 1, data);
537                GradientColor(gfxDstBuffer, invalidatedArea, data);
538                data->ClearData();
539                data->AddPoints(pointArrayBack, dataCount);
540                UIFree(pointArrayBack);
541            } else {
542                GradientColor(gfxDstBuffer, invalidatedArea, data);
543            }
544        }
545        if (data->GetHideCount() != 0) {
546            uint16_t hideIndex = data->GetHideIndex();
547            DrawPolyLine(gfxDstBuffer, 0, hideIndex, invalidatedArea, data);
548            DrawPolyLine(gfxDstBuffer, hideIndex + data->GetHideCount(), dataCount - 1, invalidatedArea, data);
549        } else {
550            DrawPolyLine(gfxDstBuffer, 0, dataCount - 1, invalidatedArea, data);
551        }
552
553        data->DrawPoint(gfxDstBuffer, invalidatedArea);
554    }
555}
556
557bool UIChartPolyline::Smooth(uint16_t startPos,
558                             Point& start,
559                             Point& end,
560                             Point& current,
561                             UIChartDataSerial* data,
562                             uint16_t& slope,
563                             uint16_t& preSlope)
564{
565    data->GetPoint(startPos + 1, current);
566    if (((end.y - start.y <= 0) && (current.y - end.y <= 0)) || ((end.y - start.y >= 0) && (current.y - end.y >= 0))) {
567        slope = (current.x == start.x) ? QUARTER_IN_DEGREE : FastAtan2(current.x - start.x, current.y - start.y);
568        if (MATH_ABS(slope - preSlope) < SMOOTH_SLOPE_ANGLE) {
569            end = current;
570            return false;
571        }
572    }
573    preSlope = (current.x == end.x) ? QUARTER_IN_DEGREE : FastAtan2(current.x - end.x, current.y - end.y);
574    return true;
575}
576
577void UIChartPolyline::GetDataBySmooth(uint16_t startIndex, uint16_t endIndex, UIChartDataSerial* data)
578{
579    if (data == nullptr) {
580        return;
581    }
582    Point* pointArray = static_cast<Point*>(UIMalloc(sizeof(Point) * data->GetDataCount()));
583    Point start;
584    Point end;
585
586    uint16_t slope;
587    data->GetPoint(startIndex, start);
588    data->GetOriginalPoint(startIndex, pointArray[0]);
589    int count = 1;
590    data->GetPoint(startIndex + 1, end);
591    uint16_t preSlope = (start.x == end.x) ? QUARTER_IN_DEGREE : FastAtan2(end.x - start.x, end.y - start.y);
592    Point current;
593    for (uint16_t i = startIndex; i < endIndex; i++) {
594        if (!Smooth(i, start, end, current, data, slope, preSlope)) {
595            continue;
596        }
597        data->GetOriginalPoint(i, pointArray[count++]);
598        start = end;
599        end = current;
600    }
601    data->GetOriginalPoint(endIndex, pointArray[count++]);
602    data->ClearData();
603    data->AddPoints(pointArray, count);
604    UIFree(pointArray);
605}
606
607void UIChartPolyline::DrawSmoothPolyLine(BufferInfo& gfxDstBuffer,
608                                         uint16_t startIndex,
609                                         uint16_t endIndex,
610                                         const Rect& invalidatedArea,
611                                         UIChartDataSerial* data)
612{
613    if (data == nullptr) {
614        return;
615    }
616    Point start;
617    Point end;
618    ColorType color = data->GetLineColor();
619    Style style = *style_;
620    style.lineColor_ = color;
621    style.lineOpa_ = OPA_OPAQUE;
622    uint16_t slope;
623    data->GetPoint(startIndex, start);
624    data->GetPoint(startIndex + 1, end);
625    uint16_t preSlope = (start.x == end.x) ? QUARTER_IN_DEGREE : FastAtan2(end.x - start.x, end.y - start.y);
626    Point current;
627    BaseGfxEngine* baseGfxEngine = BaseGfxEngine::GetInstance();
628    for (uint16_t i = startIndex; i < endIndex; i++) {
629        if (!Smooth(i, start, end, current, data, slope, preSlope)) {
630            continue;
631        }
632        Rect rect;
633        rect.SetLeft(MATH_MIN(start.x, end.x) - style_->lineWidth_);
634        rect.SetRight(MATH_MAX(start.x, end.x) + style_->lineWidth_);
635        rect.SetTop(MATH_MIN(start.y, end.y) - style_->lineWidth_);
636        rect.SetBottom(MATH_MAX(start.y, end.y) + style_->lineWidth_);
637        if (!invalidatedArea.IsIntersect(rect)) {
638            start = end;
639            end = current;
640            continue;
641        }
642        baseGfxEngine->DrawLine(gfxDstBuffer, start, end, invalidatedArea, style_->lineWidth_, color, OPA_OPAQUE);
643        ArcInfo arcinfo = {{0}};
644        arcinfo.center = end;
645        arcinfo.imgPos = Point{0, 0};
646        arcinfo.radius = (style_->lineWidth_ + 1) >> 1;
647        arcinfo.startAngle = 0;
648        arcinfo.endAngle = CIRCLE_IN_DEGREE;
649
650        baseGfxEngine->DrawArc(gfxDstBuffer, arcinfo, invalidatedArea, style, OPA_OPAQUE, CapType::CAP_NONE);
651        start = end;
652        end = current;
653    }
654    baseGfxEngine->DrawLine(gfxDstBuffer, start, end, invalidatedArea, style_->lineWidth_, color, OPA_OPAQUE);
655}
656
657void UIChartPolyline::DrawPolyLine(BufferInfo& gfxDstBuffer,
658                                   uint16_t startIndex,
659                                   uint16_t endIndex,
660                                   const Rect& invalidatedArea,
661                                   UIChartDataSerial* data)
662{
663    if ((startIndex >= endIndex) || (data == nullptr)) {
664        return;
665    }
666
667    if (data->IsSmooth()) {
668        DrawSmoothPolyLine(gfxDstBuffer, startIndex, endIndex, invalidatedArea, data);
669        return;
670    }
671    Point start;
672    Point end;
673    ColorType color = data->GetLineColor();
674    Style style = *style_;
675    style.lineColor_ = color;
676    style.lineOpa_ = OPA_OPAQUE;
677    ArcInfo arcinfo = {{0}};
678    arcinfo.imgPos = Point{0, 0};
679    arcinfo.radius = (style_->lineWidth_ + 1) >> 1;
680    arcinfo.startAngle = 0;
681    arcinfo.endAngle = CIRCLE_IN_DEGREE;
682    BaseGfxEngine* baseGfxEngine = BaseGfxEngine::GetInstance();
683    for (uint16_t i = startIndex; i < endIndex - 1; i++) {
684        data->GetPoint(i, start);
685        data->GetPoint(i + 1, end);
686        Rect rect;
687        rect.SetLeft(MATH_MIN(start.x, end.x) - style_->lineWidth_);
688        rect.SetRight(MATH_MAX(start.x, end.x) + style_->lineWidth_);
689        rect.SetTop(MATH_MIN(start.y, end.y) - style_->lineWidth_);
690        rect.SetBottom(MATH_MAX(start.y, end.y) + style_->lineWidth_);
691        if (!invalidatedArea.IsIntersect(rect)) {
692            continue;
693        }
694
695        baseGfxEngine->DrawLine(gfxDstBuffer, start, end, invalidatedArea, style_->lineWidth_, color, OPA_OPAQUE);
696        if (style_->lineWidth_ >= LINE_JOIN_WIDTH) {
697            arcinfo.center = end;
698            baseGfxEngine->DrawArc(gfxDstBuffer, arcinfo, invalidatedArea, style, OPA_OPAQUE, CapType::CAP_NONE);
699        }
700    }
701    data->GetPoint(endIndex - 1, start);
702    data->GetPoint(endIndex, end);
703    baseGfxEngine->DrawLine(gfxDstBuffer, start, end, invalidatedArea, style_->lineWidth_, color, OPA_OPAQUE);
704}
705
706bool UIChartPolyline::GetLineCrossPoint(const Point& p1,
707                                        const Point& p2,
708                                        const Point& p3,
709                                        const Point& p4,
710                                        Point& cross)
711{
712    /* Rectangular ranges of line segments must intersect. */
713    if ((MATH_MIN(p1.x, p2.x) <= MATH_MAX(p3.x, p4.x)) && (MATH_MIN(p3.x, p4.x) <= MATH_MAX(p1.x, p2.x)) &&
714        (MATH_MIN(p1.y, p2.y) <= MATH_MAX(p3.y, p4.y)) && (MATH_MIN(p3.y, p4.y) <= MATH_MAX(p1.y, p2.y))) {
715        /* Check whether the lines are parallel. If the lines are collinear, there is no intersection point. */
716        if ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y) != 0) {
717            /*
718             * (y1 - y2)x + (x2 - x1)y = x2y1 - x1y2  ->  ax + by = c
719             * (y3 - y4)x + (x4 - x3)y = x4y3 - x3y4  ->  dx + ey = f
720             */
721            int64_t a = p1.y - p2.y;
722            int64_t b = p2.x - p1.x;
723            int64_t c = p2.x * p1.y - p1.x * p2.y;
724            int64_t d = p3.y - p4.y;
725            int64_t e = p4.x - p3.x;
726            int64_t f = p4.x * p3.y - p3.x * p4.y;
727            int64_t left = a * e - b * d;
728            int64_t right = c * e - b * f;
729            if (left == 0) {
730                return false;
731            }
732            cross.x = static_cast<int16_t>(right / left);
733            left = b * d - a * e;
734            right = c * d - a * f;
735            if (left == 0) {
736                return false;
737            }
738            cross.y = static_cast<int16_t>(right / left);
739            if ((cross.x >= MATH_MIN(p1.x, p2.x)) && (cross.x <= MATH_MAX(p1.x, p2.x)) &&
740                (cross.x >= MATH_MIN(p3.x, p4.x)) && (cross.x <= MATH_MAX(p3.x, p4.x))) {
741                return true;
742            }
743        }
744    }
745    if ((MATH_MIN(p1.x, p2.x) <= MATH_MAX(p3.x, p4.x)) && (MATH_MIN(p3.x, p4.x) <= MATH_MAX(p1.x, p2.x)) &&
746        (MATH_MIN(p1.y, p2.y) >= MATH_MAX(p3.y, p4.y)) && (MATH_MIN(p3.y, p4.y) <= MATH_MAX(p1.y, p2.y))) {
747        return enableReverse_ ? true : false;
748    }
749    return false;
750}
751
752void UIChartPolyline::FindCrossPoints(const ChartLine& line, const ChartLine& polyLine, CrossPointSet& cross)
753{
754    if (GetLineCrossPoint(line.start, line.end, polyLine.start, polyLine.end, cross.nextFirst)) {
755        if (enableReverse_ && (MATH_MIN(line.start.y, line.end.y) >= MATH_MAX(polyLine.start.y, polyLine.end.y))) {
756            if (!cross.firstFind) {
757                if (polyLine.start.y < polyLine.end.y) {
758                    cross.first = cross.nextFirst;
759                    cross.firstFind = false;
760                }
761            } else if (!cross.secondFind) {
762                if ((cross.first.x != cross.nextFirst.x) || (cross.first.y != cross.nextFirst.y)) {
763                    cross.second = cross.nextFirst;
764                    cross.secondFind = true;
765                    return;
766                }
767                if (polyLine.start.y > polyLine.end.y) {
768                    cross.firstFind = true;
769                }
770            }
771            return;
772        }
773        if (!cross.firstFind) {
774            /* first corss must on the line like "/" */
775            if (polyLine.start.y < polyLine.end.y) {
776                cross.first = cross.nextFirst;
777                cross.firstFind = true;
778            }
779        } else if (!cross.secondFind) {
780            /* second corss can't be same with first cross. */
781            if ((cross.first.x != cross.nextFirst.x) || (cross.first.y != cross.nextFirst.y)) {
782                cross.second = cross.nextFirst;
783                cross.secondFind = true;
784                return;
785            }
786            /* second corss must on the line like "\", otherwise skip those crosss. */
787            if (polyLine.start.y > polyLine.end.y) {
788                cross.firstFind = false;
789            }
790        }
791    }
792}
793
794void UIChartPolyline::DrawGradientColor(BufferInfo& gfxDstBuffer,
795                                        const Rect& invalidatedArea,
796                                        UIChartDataSerial* data,
797                                        const ChartLine& linePoints,
798                                        const ChartLine& limitPoints,
799                                        int16_t startY)
800{
801    if (data == nullptr) {
802        return;
803    }
804    Rect currentRect = GetContentRect();
805    CrossPointSet cross = {{0}};
806    ChartLine polyLine = {{0}};
807    uint16_t pointCount = data->GetDataCount() - 1;
808    int16_t y = enableReverse_ ? (linePoints.start.y + startY) : (startY - linePoints.start.y);
809    int16_t mixScale = !enableReverse_ ? (currentRect.GetBottom() - y) : (y - currentRect.GetTop());
810    if ((mixScale < 0) || (mixScale >= currentRect.GetHeight())) {
811        return;
812    }
813    bool onVerticalLine = enableReverse_ ? (y <= limitPoints.start.y) : (y >= limitPoints.start.y);
814    if (onVerticalLine) {
815        cross.first.x = limitPoints.start.x;
816        cross.first.y = enableReverse_ ? (y - startY) : (startY - y);
817        cross.firstFind = true;
818    }
819    Point start;
820    Point end;
821    BaseGfxEngine* baseGfxEngine = BaseGfxEngine::GetInstance();
822    for (uint16_t i = 0; i < pointCount; i++) {
823        data->GetPoint(i, start);
824        data->GetPoint(i + 1, end);
825        if (start.y == end.y) {
826            int16_t tmpY = enableReverse_ ? (start.y + startY) : (startY - start.y);
827            if (tmpY == linePoints.start.y) {
828                cross.firstFind = false;
829                cross.secondFind = false;
830            }
831            continue;
832        }
833        start.y = enableReverse_ ? (start.y - startY) : (startY - start.y);
834        end.y = enableReverse_ ? (end.y - startY) : (startY - end.y);
835        polyLine = {start, end};
836        FindCrossPoints(linePoints, polyLine, cross);
837        SetDrawLineCross(gfxDstBuffer, invalidatedArea, data, cross, baseGfxEngine, startY, mixScale);
838    }
839    if (cross.firstFind && !cross.secondFind) {
840        cross.second = {limitPoints.end.x, y};
841        cross.first.y = y;
842        baseGfxEngine->DrawLine(gfxDstBuffer, cross.first, cross.second, invalidatedArea, 1, data->GetFillColor(),
843                                mixData_[mixScale]);
844    }
845}
846
847void UIChartPolyline::SetDrawLineCross(BufferInfo& gfxDstBuffer,
848                                       const Rect& invalidatedArea,
849                                       UIChartDataSerial* data,
850                                       CrossPointSet& cross,
851                                       BaseGfxEngine* baseGfxEngine,
852                                       int16_t startY,
853                                       int16_t mixScale)
854{
855    if (cross.firstFind && cross.secondFind) {
856        cross.first.y = enableReverse_ ? (cross.first.y + startY) : (startY - cross.first.y);
857        cross.second.y = enableReverse_ ? (cross.second.y + startY) : (startY - cross.second.y);
858        baseGfxEngine->DrawLine(gfxDstBuffer, cross.first, cross.second, invalidatedArea, 1, data->GetFillColor(),
859                                mixData_[mixScale]);
860        cross.firstFind = false;
861        cross.secondFind = false;
862    }
863}
864
865void UIChartPolyline::CalcVerticalInfo(int16_t top,
866                                       int16_t bottom,
867                                       int16_t start,
868                                       int16_t end,
869                                       int16_t& y,
870                                       int16_t& yHeight)
871{
872    if ((top < start) && (bottom > start)) {
873        y = start;
874        yHeight = top;
875    } else if ((bottom <= start) && (top >= end)) {
876        y = bottom;
877        yHeight = top;
878    } else if ((top < end) && (bottom > end)) {
879        y = bottom;
880        yHeight = end;
881    }
882}
883
884void UIChartPolyline::GradientColor(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea, UIChartDataSerial* data)
885{
886    if (data == nullptr) {
887        return;
888    }
889    int16_t bottom = invalidatedArea.GetBottom();
890    int16_t top = invalidatedArea.GetTop();
891    Point yStart = yAxis_.GetStartPoint();
892    yStart.y = enableReverse_ ? (yStart.y + gradientBottom_) : (yStart.y - gradientBottom_);
893    int16_t topY = enableReverse_ ? data->GetValleyData() : data->GetPeakData();
894    int16_t bottomY = enableReverse_ ? data->GetPeakData() : data->GetValleyData();
895    yAxis_.TranslateToPixel(topY);
896    yAxis_.TranslateToPixel(bottomY);
897    int16_t valleyY = enableReverse_ ? topY : bottomY;
898    int16_t startY = enableReverse_ ? topY : yStart.y;
899    int16_t endY = enableReverse_ ? yStart.y : topY;
900    if ((bottom < endY) || (top > startY)) {
901        return;
902    }
903
904    int16_t y = 0;
905    int16_t yHeight = 0;
906    CalcVerticalInfo(top, bottom, startY, endY, y, yHeight);
907
908    ChartLine limitPoints = {{0}};
909    data->GetPoint(0, limitPoints.start);
910    data->GetPoint(data->GetDataCount() - 1, limitPoints.end);
911    ChartLine linePoints = {{0}};
912    linePoints.start.x = limitPoints.start.x;
913    linePoints.end.x = limitPoints.end.x;
914    Rect currentRect = GetContentRect();
915    BaseGfxEngine* baseGfxEngine = BaseGfxEngine::GetInstance();
916    while (y >= yHeight) {
917        linePoints.start.y = enableReverse_ ? (y - endY) : (startY - y);
918        linePoints.end.y = linePoints.start.y;
919        if (y <= valleyY) {
920            int16_t baseY = enableReverse_ ? endY : startY;
921            DrawGradientColor(gfxDstBuffer, invalidatedArea, data, linePoints, limitPoints, baseY);
922        } else {
923            int16_t mixScale = enableReverse_ ? (linePoints.start.y + endY - currentRect.GetTop()) :
924                                                (currentRect.GetBottom() - (startY - linePoints.start.y));
925            if ((mixScale < 0) || (mixScale >= currentRect.GetHeight())) {
926                y--;
927                continue;
928            }
929            Point start = {limitPoints.start.x, y};
930            Point end = {limitPoints.end.x, y};
931            baseGfxEngine->DrawLine(gfxDstBuffer, start, end, invalidatedArea, 1, data->GetFillColor(),
932                                    mixData_[mixScale]);
933        }
934        y--;
935    }
936}
937} // namespace OHOS
938