属于同一个分支 #1

Closed
Lee0706 wants to merge 19 commits from main into master
335 changed files with 17403 additions and 1 deletions

202
LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please also get an ISBN
number for the printed version if you plan to publish a book
using this license. We recommend considering the
Apache License 2.0 for software, and the CC-BY license for
other creative works.
Copyright 2026 LI Meng
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,2 +1,2 @@
# TSZQ_KaiYuanHongMengRISC-VJuShenZhiNengKeChengGongJianZhuanQu
计算思维程序实践共14个计算思维程序实践项目编写开源鸿蒙应用程序为学生系统剖析了每一个实践项目的计算思维为学生搭建了一个五级阶梯的程序实现路径同时为学生提供了由零基础开发到最终系统实现的初始版程序和程序实现环境供学生上机调试与研讨。

21
datafiting/README.md Normal file
View File

@ -0,0 +1,21 @@
# datafiting
### 1、项目说明
总体思路曲线拟合绘制程序项目开发的总体思路如图所示以实现交互界面与交互控制和数据结构和数值分析算法等。程序有两个主体功能1对设置的数据集和用户选择的拟合曲线类型进行拟合并计算出拟合优度和函数表达式2根据拟合得到的函数表达式进行绘制。
图1.1 项目开发的总体思路图
<img src="figs/overall.png" width=80% /></center>
### 2、开发环境说明
1系统版本OpenHarmony 4.0 Release
2SDK版本API 10
3Model类型Stage
4DevEco Studio版本Deveco Studio 4.0 release
5硬件环境Unionpi Whale、RK3566开发板
6预览器参数(1)DeviceType:default;(2)Resolution:560-1280;(3)DPI:240;其余参数自定。
### 3、项目主要函数调用关系图与项目输出界面演进图
图3.1 项目主要函数调用关系图
<img src="figs/relation.png" width=80% /></center>
图3.2 项目输出界面演进图
<img src="figs/evolution.png" width=80% /></center>

11
datafiting/datafit/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
/node_modules
/oh_modules
/local.properties
/.idea
**/build
/.hvigor
.cxx
/.clangd
/.clang-format
/.clang-tidy
**/.test

View File

@ -0,0 +1,10 @@
{
"app": {
"bundleName": "com.test.myapplication",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:app_icon",
"label": "$string:app_name"
}
}

View File

@ -0,0 +1,8 @@
{
"string": [
{
"name": "app_name",
"value": "MyApplication"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,49 @@
{
"app": {
"signingConfigs": [
{
"name": "default",
"material": {
"certpath": "C:\\Users\\nInse\\.ohos\\config\\openharmony\\auto_ohos_default_draw2_com.test.myapplication.cer",
"storePassword": "0000001B23074682D90DCDA10157D8187E84EC05F50638D319B2E510345811D5FEB541650A0415030813F5",
"keyAlias": "debugKey",
"keyPassword": "0000001B291E996BCAB68900B133F9F5D394B9749AFD0C20146A22CE85070C1340F65B45506D15A417CC08",
"profile": "C:\\Users\\nInse\\.ohos\\config\\openharmony\\auto_ohos_default_draw2_com.test.myapplication.p7b",
"signAlg": "SHA256withECDSA",
"storeFile": "C:\\Users\\nInse\\.ohos\\config\\openharmony\\auto_ohos_default_draw2_com.test.myapplication.p12"
}
}
],
"products": [
{
"name": "default",
"signingConfig": "default",
"compileSdkVersion": 10,
"compatibleSdkVersion": 10,
"runtimeOS": "OpenHarmony",
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}

6
datafiting/datafit/entry/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test

View File

@ -0,0 +1,31 @@
{
"apiType": "stageMode",
"buildOption": {
"arkOptions": {
// "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}

View File

@ -0,0 +1,6 @@
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@ -0,0 +1,18 @@
# Define project specific obfuscation rules here.
# You can include the obfuscation configuration files in the current module's build-profile.json5.
#
# For more details, see
# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
# Obfuscation options:
# -disable-obfuscation: disable all obfuscations
# -enable-property-obfuscation: obfuscate the property names
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
# -compact: remove unnecessary blank spaces and all line feeds
# -remove-log: remove all console.* statements
# -print-namecache: print the name cache that contains the mapping from the old names to new names
# -apply-namecache: reuse the given cache file
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope

View File

@ -0,0 +1,10 @@
{
"license": "",
"devDependencies": {},
"author": "",
"name": "entry",
"description": "Please describe the basic information.",
"main": "",
"version": "1.0.0",
"dependencies": {}
}

View File

@ -0,0 +1,30 @@
export class COMMON_VALUE{
/**
* 字体大小(大)
*/
static readonly BIG_SIZE_FONT = 20
/**
* 字体大小(小)
*/
static readonly SMALL_SIZE_FONT = 15
/**
* 字体大小(超小)
*/
static readonly SMALLER_SIZE_FONT = 10
/**
* 浅蓝
*/
static readonly LIGHT_BLUE = '#87BAD4'
/**
* 紫色
*/
static readonly PURPLE = '#BE4AD9'
/**
* 灰色
*/
static readonly GREY = '#B8B8B8'
/**
* 浅白
*/
static readonly LIGHT_WHITE = '#F2F2F2'
}

View File

@ -0,0 +1,221 @@
import promptAction from '@ohos.promptAction'
import { COMMON_VALUE } from '../common/commonValue'
import { handle } from '../utils/algorithm/createPotGather'
import { BoardInfo, Quadrant } from '../utils/drawBoard/BoardType'
import { IPoint, PointType } from '../utils/drawBoard/DrawPoint'
import { funcInfo, functionUtilIns } from '../utils/FunctionUtil'
@Component
export struct controlBar {
@State func:funcInfo|undefined = undefined
//TODO
@Prop fitFuncList:Array<funcInfo>
@Prop selectFitFunc:funcInfo
@Prop curFuncIndex:number
@Link isStartFit:boolean
@Link isShowFunc:boolean
@Link quadrant:Quadrant
@Link points:Array<IPoint>
@Link selectFuncIndex:number
@Consume board:BoardInfo
@Consume isRefreshPoint:boolean
@Consume isRefreshFunc:boolean
controller:CustomDialogController = new CustomDialogController({
builder:addPoints({points:$points,isStartFit:$isStartFit,isShowFunc:$isShowFunc})
})
build() {
Column(){
Row({space:5}){
Row(){
Text('坐标轴')
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
Radio({value:"四象限",group:"Quadrant"})
.checked(this.quadrant == Quadrant.FOUR)
.onClick(()=>{
this.quadrant = Quadrant.FOUR
AppStorage.setOrCreate("quadrant",Quadrant.FOUR)
})
Text('四象限')
.fontSize(COMMON_VALUE.SMALLER_SIZE_FONT)
Radio({value:"一象限",group:"Quadrant"})
.checked(this.quadrant == Quadrant.ONE)
.onClick(()=>{
this.quadrant = Quadrant.ONE
AppStorage.setOrCreate("quadrant",Quadrant.ONE)
})
Text('一象限')
.fontSize(COMMON_VALUE.SMALLER_SIZE_FONT)
}.backgroundColor(COMMON_VALUE.GREY)
Row({space:5}){
Button('生成数据集')
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
.type(ButtonType.Normal)
.onClick(()=>{
this.isRefreshPoint = !this.isRefreshPoint
this.points = this.board.initMap()
this.board.drawAxis()
this.board.drawPoints()
})
Button('显示数据集')
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
.type(ButtonType.Normal)
.onClick(()=>{
this.isShowFunc = false
})
Button('显示数据集与曲线')
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
.type(ButtonType.Normal)
.onClick(()=>{
if(this.points.length == 0){
promptAction.showToast({
message:"还未选择样本点!"
})
return
}
if(this.selectFuncIndex != this.curFuncIndex){
this.isShowFunc = false
this.selectFuncIndex = this.curFuncIndex
}
setTimeout(()=>{
this.isStartFit = false
if (this.isShowFunc == false) {
this.isShowFunc = true
}
},100)
})
Button('手动输入数据集')
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
.type(ButtonType.Normal)
.onClick(()=>{
this.controller.open()
})
Button('拟合')
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
.type(ButtonType.Normal)
.onClick(()=>{
if(this.points.length == 0){
promptAction.showToast({
message:"还未选择样本点!"
})
return
}
//TODO
this.selectFitFunc = this.fitFuncList[this.curFuncIndex]
this.func = functionUtilIns.getFitFunc(this.selectFitFunc,this.points)
this.isStartFit = true
this.isShowFunc = false
setTimeout(()=>{
this.isShowFunc = true
},100)
this.selectFuncIndex = this.curFuncIndex
})
}
}
Row(){
Text('结果输出:')
Swiper(){
if(this.isStartFit){
Text('函数形式:' + this.selectFitFunc.name + "\t" + this.selectFitFunc.body)
.width('100%').height('100%')
Text('函数系数:' + this.func!.body)
.width('100%').height('100%')
Text('拟合优度R^2:' + this.func!.R!)
.width('100%').height('100%')
}
}.backgroundColor(COMMON_VALUE.GREY).layoutWeight(1).vertical(true).height('100%').autoPlay(false).indicator(false).loop(false)
}.width('100%').layoutWeight(1).padding(5)
}.backgroundColor(COMMON_VALUE.LIGHT_WHITE).width('100%').height('100%')
}
}
@CustomDialog
struct addPoints{
controller:CustomDialogController
@State setNum:number = -1
@State num:number = 0
@State msg:string = ''
@Link points:Array<IPoint>
@Link isStartFit:boolean
@Link isShowFunc:boolean
@Consume board:BoardInfo
@Consume isRefreshPoint:boolean
@Consume isRefreshFunc:boolean
build(){
Column({space:5}){
Row(){
if(this.setNum == -1){
Text('设置样本点个数')
TextInput({text:this.num.toString()})
.onChange((value)=>{
this.num = Number(value)
})
}
}
if(this.setNum == -1){
Row(){
Button('确定')
.onClick(()=>{
if(this.num <= 0){
promptAction.showToast({
message:"必须为正整数"
})
return
}
this.points = []
this.setNum = this.num
})
.type(ButtonType.Normal)
Button('取消')
.onClick(()=>{
this.controller.close()
})
.type(ButtonType.Normal)
}.width('100%').justifyContent(FlexAlign.SpaceAround)
}else {
Column(){
if(this.setNum == 0){
Text('是否保存数据集')
}else{
Text("输入" + this.setNum + "个样本点,格式为X,X")
TextInput({text:this.msg})
.onChange((value)=>{
this.msg = value
})
}
Row(){
Button('确定')
.onClick(()=>{
if(this.setNum == 0){
this.isRefreshPoint = !this.isRefreshPoint
this.board.initMap(this.points)
this.board.drawAxis()
this.board.drawPoints()
this.controller.close()
}else{
let arr = this.msg.split(',')
this.points.push({x:Number(arr[0]),y:Number(arr[1]),pointType:PointType.CONTINUE})
this.msg = ''
this.setNum--
}
})
.type(ButtonType.Normal)
Button('取消')
.onClick(()=>{
this.controller.close()
})
.type(ButtonType.Normal)
}.width('100%').justifyContent(FlexAlign.SpaceAround)
}
}
}.padding(5)
}
}

View File

@ -0,0 +1,102 @@
import { COMMON_VALUE } from '../common/commonValue'
import { funcInfo } from '../utils/FunctionUtil'
@Component
export struct fitFuncType {
@Link fitFuncList:Array<funcInfo>
@Link curFuncIndex:number
controller:CustomDialogController = new CustomDialogController({
builder:addFitFuncType({fitFuncList:$fitFuncList})
})
build() {
Column({space:5}){
Text('拟合曲线类型')
.height('12%')
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
List(){
ForEach(this.fitFuncList,(item:funcInfo,index:number)=>{
ListItem(){
funcItem({curFuncIndex:$curFuncIndex,func:item,funcIndex:index})
}.width('100%').height(30)
})
}.width('100%').layoutWeight(1).backgroundColor(COMMON_VALUE.LIGHT_BLUE)
Button('新增拟合曲线类型')
.onClick(()=>{
this.controller.open()
})
.type(ButtonType.Normal)
.backgroundColor(COMMON_VALUE.PURPLE)
}.width('100%').height('100%').padding({top:5,bottom:5,right:5}).backgroundColor(COMMON_VALUE.LIGHT_WHITE)
}
}
@Component
struct funcItem {
@Link curFuncIndex:number
@Prop func:funcInfo
@Prop funcIndex:number
build() {
Row({space:5}){
Radio({value:this.func.name,group:"fitFunc"})
.width(20)
.height(20)
.checked(this.curFuncIndex == this.funcIndex)
.onChange((value)=>{
if(this.curFuncIndex == this.funcIndex){
return
}
this.curFuncIndex = this.funcIndex
})
Text(this.func.name)
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
Text(this.func.body)
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
}.width('100%').height('100%')
}
}
let funcList:Array<funcInfo> = [
{name:"对数函数",body:"a*log(x,b)+c"},
{name:"指数函数",body:"a*e^(0.01x)+b"}
]
@CustomDialog
struct addFitFuncType{
@Link fitFuncList:Array<funcInfo>
controller:CustomDialogController
@State index:number = 0
@Builder item(index:number){
Row(){
Text(funcList[index].name)
.fontColor(this.index == index?Color.Red:Color.Black)
Text(funcList[index].body)
.fontColor(this.index == index?Color.Red:Color.Black)
}.width('100%').onClick(()=>{
this.index = index
})
}
build(){
Column({space:5}){
ForEach(funcList,(item:funcInfo,index)=>{
this.item(index)
})
Row(){
Button('确定')
.onClick(()=>{
this.fitFuncList.push({name:funcList[this.index].name,body:funcList[this.index].body})
funcList.splice(this.index,1)
this.controller.close()
})
.type(ButtonType.Normal)
Button('取消')
.onClick(()=>{
this.controller.close()
})
.type(ButtonType.Normal)
}.width('100%').justifyContent(FlexAlign.SpaceAround)
}.width('100%').padding(5)
}
}

View File

@ -0,0 +1,65 @@
import { COMMON_VALUE } from '../common/commonValue'
import { BoardInfo } from '../utils/drawBoard/BoardType'
import { IPoint, PointType } from '../utils/drawBoard/DrawPoint'
import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import { createDocument } from '../model/createDocumentModel';
import buffer from '@ohos.buffer';
@Component
export struct header {
@State filePath:string = ''
@Link points:Array<IPoint>
@Link isStartFit:boolean
@Link isShowFunc:boolean
@Consume board:BoardInfo
@Consume isRefreshPoint:boolean
@Consume isRefreshFunc:boolean
context = getContext()
build() {
Row({space:5}){
Text('样本数据集文件')
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
Text(this.filePath)
.textOverflow({overflow:TextOverflow.MARQUEE})
.layoutWeight(1)
.fontSize(COMMON_VALUE.SMALL_SIZE_FONT)
.backgroundColor(COMMON_VALUE.LIGHT_BLUE)
.height('100%')
Button('装载')
.type(ButtonType.Normal)
.onClick(async ()=>{
this.points = []
let documentPicker = new picker.DocumentViewPicker()
let DocumentSelectOptions = new picker.DocumentSelectOptions();
let DocumentSelectResult: Array<string> = await documentPicker.select(DocumentSelectOptions)
let uri = DocumentSelectResult[0]
let file = fs.openSync(uri,fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
//已经获取到选择的文件 进行读取操作
let arrayBuffer = new ArrayBuffer(8)
let size = 0
let dSize = 0
let str = fs.readTextSync(file.path)
//解析路径
this.filePath = file.path
fs.closeSync(file)
//解析文件样本点
let arr = str.split('\n')
for (let index = 0; index < arr.length; index++) {
this.points.push({x:Number(arr[index].split(' ')[0]),y:Number(arr[index].split(' ')[1]),pointType:PointType.CONTINUE})
}
this.isStartFit = false
this.isShowFunc = false
this.isRefreshPoint = !this.isRefreshPoint
this.board.initMap(this.points)
this.board.drawPoints()
this.board.drawAxis()
})
}.width('100%').height('100%').padding(5).backgroundColor(COMMON_VALUE.LIGHT_WHITE)
}
}

View File

@ -0,0 +1,31 @@
import { BoardInfo, CONTEXT, FIT_CONTEXT } from '../utils/drawBoard/BoardType'
import { IPoint } from '../utils/drawBoard/DrawPoint'
import { funcInfo, functionUtilIns } from '../utils/FunctionUtil'
@Component
export struct imageShow {
@Prop loop_board:string
@Prop loop_fit:string
@Prop isShowFunc:boolean
@Prop points:Array<IPoint>
@Prop selectFitFunc:funcInfo
@Consume board:BoardInfo
@Consume isRefreshFunc:boolean
build() {
Stack(){
Canvas(CONTEXT)
.width('100%').height(this.loop_board)
if(this.isShowFunc){
Canvas(FIT_CONTEXT)
.width('100%').height(this.loop_fit)
.onReady(()=>{
//获取到经过拟合后的函数
functionUtilIns.getFitFunc(this.selectFitFunc,this.points)
//解析函数 获取对应坐标点
let points:Array<IPoint> = functionUtilIns.getPoints()
this.board.drawFitFunc(points)
})
}
}
}
}

View File

@ -0,0 +1,43 @@
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}

View File

@ -0,0 +1,19 @@
import picker from '@ohos.file.picker';
import { BusinessError } from '@ohos.base';
import fs from '@ohos.file.fs';
/**
* 创建一个包含样本点的文件 用于读取测试
*/
export let createDocument = async ()=>{
let documentPicker = new picker.DocumentViewPicker();
let uris:Array<string> = []
let DocumentSaveOptions = new picker.DocumentSaveOptions();
DocumentSaveOptions.newFileNames = ['testPoints.txt']
uris = await documentPicker.save(DocumentSaveOptions)
let uri = uris[0]
let file = fs.openSync(uri, fs.OpenMode.WRITE_ONLY);
let writeLen = 0
writeLen += fs.writeSync(file.fd, '2 2\n100 100\n123 323\n200.99 199.999\n302.8 300.6\n400 400.9\n450 450');//todo必须加结束位 否则多读
fs.closeSync(file);
}

View File

@ -0,0 +1,105 @@
import { controlBar } from '../component/ControlBar'
import { fitFuncType } from '../component/FitFuncType'
import { header } from '../component/Header'
import { imageShow } from '../component/ImageShow'
import { createDocument } from '../model/createDocumentModel'
import { B1Q2D, B4Q2D, BoardInfo, Quadrant } from '../utils/drawBoard/BoardType'
import { IPoint } from '../utils/drawBoard/DrawPoint'
import { funcInfo } from '../utils/FunctionUtil'
@Entry
@Component
struct Index {
@State fitFuncList:Array<funcInfo> = [
{name:"一次函数",body:"a*x+b"},
{name:"二次函数",body:"a*x^2+b*x+c"},
{name:"三次函数",body:"a*x^3+b*x^2+c*x+d"},
{name:"四次函数",body:"a*x^4+b*x^3+c*x^2+d*x+e"}
]
@State curFuncIndex:number = 0
@State selectFuncIndex:number = 0
/**
* 是否开始拟合 点击拟合按钮改为true
* 响应:结果输出栏
*/
@State isStartFit:boolean = false
/**
* 是否展示拟合函数 点击显示数据集与曲线改为true
* 响应:出现函数图像
*/
@State isShowFunc:boolean = false
@State @Watch('refreshBoard')quadrant:Quadrant = Quadrant.FOUR
@Provide board:BoardInfo = B4Q2D
@Provide @Watch('refreshPoint')isRefreshPoint:boolean = false
@Provide @Watch('refreshFunc')isRefreshFunc:boolean = false
@State loop_board:string = '100%'
@State loop_fit:string = '100%'
@State virtualPoints:Array<IPoint> = []
@State boardPoints:Array<IPoint> = []
refreshPoint(){
if(this.loop_board == '100%'){
this.loop_board = '101%'
}else {
this.loop_board = '100%'
}
console.log('REFRESH point')
this.isStartFit = false
this.isShowFunc = false
}
refreshFunc(){
if(this.loop_fit == '100%'){
this.loop_fit = '101%'
}else {
this.loop_fit = '100%'
}
console.log('REFRESH func')
}
refreshBoard(){
console.log('REFRESH board')
if(this.quadrant == Quadrant.ONE){
this.board = B1Q2D
}else{
this.board = B4Q2D
}
if(this.virtualPoints.length != 0){
this.isRefreshPoint = !this.isRefreshPoint
this.isRefreshFunc = !this.isRefreshFunc
this.board.initMap(this.virtualPoints)
this.board.drawAxis()
this.board.drawPoints()
this.isStartFit = false
}
}
async aboutToAppear(){
await createDocument()
}
build() {
Column(){
Row(){
Column(){
header({points:$virtualPoints,isShowFunc:$isShowFunc,isStartFit:$isStartFit}).width('100%').height('15%')
imageShow({points:this.virtualPoints,selectFitFunc:this.fitFuncList[this.curFuncIndex],loop_board:this.loop_board,loop_fit:this.loop_fit,isShowFunc:this.isShowFunc}).width('100%').height('85%')
}.width('60%').height('100%')
fitFuncType({fitFuncList:$fitFuncList,curFuncIndex:$curFuncIndex}).width('40%').height('100%')
}.width('100%').height('75%')
//TODO
controlBar({points:$virtualPoints,isShowFunc:$isShowFunc,quadrant:$quadrant,isStartFit:$isStartFit,selectFitFunc:this.fitFuncList[this.selectFuncIndex],selectFuncIndex:$selectFuncIndex,curFuncIndex:this.curFuncIndex,fitFuncList:this.fitFuncList}).width('100%').layoutWeight(1)
}.width('100%').height('100%')
}
}

View File

@ -0,0 +1,49 @@
import { IPoint, PointType } from './drawBoard/DrawPoint'
import { handle } from './algorithm/createPotGather'
export interface funcInfo {
/**
* 名称
*/
name: string
/**
* 函数体
*/
body: string
/**
* 拟合优度
*/
R?: number
}
/**
* 提供函数拟合的方法
*/
class FunctionUtil {
/**
* 根据拟合函数类型和样本点计算出拟合函数
* 返回funcInfo
*/
getFitFunc(fitFunc: funcInfo, points: Array<IPoint>) {
handle.fit(fitFunc.body, points);
let func: funcInfo = { name: "", body: handle.fitFunc, R: handle.R_squared }
return func
}
/**
* 根据函数返回xyMap
*/
getPoints() {
//TODO 结合原代码
//用来测试的拟合函数
handle.compote_curveData();
let point: Array<IPoint> = handle.coordinatePoint;
// console.log(handle.fitFunc)
// for (let i = 0; i < point.length; i++) {
// console.log("testTag", "(" + point[i].x + ", " + point[i].y + ")");
// }
return point
}
}
export let functionUtilIns = new FunctionUtil()

View File

@ -0,0 +1,159 @@
import { mathjs } from './evaluate';
import {IPoint, PointType} from "../drawBoard/DrawPoint"
import {FitRes} from "./fittingResult"
import { Matrix } from './least_square';
import { Quadrant } from '../drawBoard/BoardType';
import { Exponent } from './exponentialFit';
import { Logarithm } from './logarithmicFit';
function arrange(low: number, high: number, step: number): number[] {
const rangeArray: number[] = [];
if (step <= 0) {
throw new Error("Step must be greater than zero.");
}
for (let i = low; i <= high; i += step) {
rangeArray.push(i);
}
return rangeArray;
}
const replaceVar: Array<string> = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
class CreatePointGather {
coordinatePoint: Array<IPoint>;
theta: number[];
error: number;
R_squared: number;
fitFunc: string;
funcFormat: string;
private m: number;
constructor() {
this.coordinatePoint = [];
this.theta = [];
this.error = 0;
this.R_squared = 0;
this.fitFunc = "";
this.funcFormat = "";
this.m = 0;
}
private judgeWhichM(funcFormat: string) {
const index = funcFormat.indexOf("^");
if (index === -1) {
this.m = 1;
} else {
this.m = Number(funcFormat[index + 1]);
}
}
private getFitFunc(funcFormat: string): void {
this.funcFormat = funcFormat;
this.fitFunc = funcFormat;
if (funcFormat == "a*e^(0.01x)+b") {
for (let i = 0; i < 2; i++) {
if (this.theta[i] < 0 && this.fitFunc[this.fitFunc.indexOf(replaceVar[i]) - 1] == "+") {
this.fitFunc = this.fitFunc.substring(0, this.fitFunc.indexOf(replaceVar[i]) - 1) + this.fitFunc.substring(this.fitFunc.indexOf(replaceVar[i]));
}
this.fitFunc = this.fitFunc.replace(replaceVar[i], this.theta[i].toFixed(4));
}
} else if (funcFormat == "a*log(x,b)+c") {
for (let i = 0; i < 3; i++) {
if (this.theta[i] < 0 && this.fitFunc[this.fitFunc.indexOf(replaceVar[i]) - 1] == "+") {
this.fitFunc = this.fitFunc.substring(0, this.fitFunc.indexOf(replaceVar[i]) - 1) + this.fitFunc.substring(this.fitFunc.indexOf(replaceVar[i]));
}
this.fitFunc = this.fitFunc.replace(replaceVar[i], this.theta[i].toFixed(4));
}
} else {
for (let i = 0; i < this.m + 1; i++) {
if (this.theta[i] < 0 && this.fitFunc[this.fitFunc.indexOf(replaceVar[i]) - 1] == "+") {
this.fitFunc = this.fitFunc.substring(0, this.fitFunc.indexOf(replaceVar[i]) - 1) + this.fitFunc.substring(this.fitFunc.indexOf(replaceVar[i]));
}
this.fitFunc = this.fitFunc.replace(replaceVar[i], this.theta[i].toFixed(4));
}
}
}
private calculate_function(x:number): number {
if (this.funcFormat == "a*e^(0.01x)+b") {
return Exponent.expFunc(x, this.theta[0], this.theta[1]);
} else if (this.funcFormat == "a*log(x,b)+c") {
if (x > 0) return Logarithm.logFunc(x, this.theta[0], this.theta[1], this.theta[2]);
else return Number.NaN;
}
return mathjs.evaluate(this.fitFunc, [{char: 'x', value: x}]);
}
compote_curveData(low: number = -500, high: number = 500, step: number = 1): Array<IPoint> {
if (AppStorage.get("quadrant") == Quadrant.ONE) {
low = 0;
high = 1000;
}
this.coordinatePoint = [];
let xValues = arrange(low, high, step);
let yValues: number[] = [];
let size = xValues.length;
yValues = Array<number>(size).fill(0).map((_, index) => {
return this.calculate_function(xValues[index]);
})
for (let i = 0; i < size; i++) {
if (!Number.isNaN(yValues[i])) {
this.coordinatePoint.push({
x: xValues[i],
y: yValues[i],
pointType: PointType.CONTINUE
});
}
}
if (!this.coordinatePoint.length) {
this.coordinatePoint.push({
x: 0,
y: 0,
pointType: PointType.CONTINUE
})
}
return this.coordinatePoint;
}
private error_func(sampleData: Array<IPoint>): FitRes {
let sx: number[] = sampleData.map(value => value.x);
let sy: number[] = sampleData.map(value => value.y);
let y_predict: number[] = Array<number>(sx.length).fill(0).map((_, index) => {
return this.calculate_function(sx[index]);
})
let error: number = 0;
for (let i = 0; i < sx.length; i++) {
error += (sy[i] - y_predict[i]) ** 2;
}
const mean = sy.reduce((acc, val) => acc + val, 0) / sy.length;
let sum: number = sy.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0);
let R_squared: number = 1 - (error/sum);
if (R_squared <= 0) R_squared = 0;
let fitRes: FitRes = {
error: error,
R_squared: R_squared
};
return fitRes;
}
fit(funcFormat: string, sampleData: Array<IPoint>): void {
let fitRes: FitRes = {};
if (funcFormat == "a*e^(0.01x)+b") {
this.theta = Exponent.gradientDescentExp(sampleData);
} else if (funcFormat == "a*log(x,b)+c") {
this.theta = Logarithm.gradientDescentLog(sampleData);
} else {
this.judgeWhichM(funcFormat);
fitRes = Matrix.leastSquareMethod(this.m, sampleData);
if (fitRes.theta) this.theta = fitRes.theta;
}
this.getFitFunc(funcFormat);
fitRes = this.error_func(sampleData);
if (fitRes.error && fitRes.R_squared) {
this.error = fitRes.error;
this.R_squared = fitRes.R_squared;
}
}
}
export let handle: CreatePointGather = new CreatePointGather();

View File

@ -0,0 +1,208 @@
interface replaceableVar {
char: string,
value: number
}
class MathExpressionParser {
private tokens: string[];
private currentTokenIndex: number;
private variables: replaceableVar[];
constructor() {
this.tokens = []
this.currentTokenIndex = 0
this.variables = []
}
private tokenize(expression: string): string[] {
const regex: RegExp = new RegExp("(\\d+\\.?\\d*|\\.\\d+|[a-zA-Z]+|\\S)", "g");
const originalTokens = expression.match(regex) || [];
const Tokens: string[] = [];
for (let i = 0; i < originalTokens.length; i++) {
Tokens.push(originalTokens[i]);
if (i < originalTokens.length - 1) {
const currentToken = originalTokens[i];
const nextToken = originalTokens[i + 1];
if ((this.isNumber(currentToken) && this.isVariable(nextToken)) ||
(this.isVariable(currentToken) && this.isVariable(nextToken)) ||
(currentToken === ')' && (this.isVariable(nextToken) || this.isNumber(nextToken))) ||
(this.isNumber(currentToken) && nextToken === '(') ||
(this.isVariable(currentToken) && nextToken === '(')) {
Tokens.push('*');
}
}
}
return Tokens;
}
private existInArr(token: string): number {
for (let i: number = 0; i < this.variables.length; i++) {
if (this.variables[i].char == token) {
return i;
}
}
return -1;
}
private isNumber(token: string): boolean {
const num = Number(token);
return !Number.isNaN(num) && Number.isFinite(num);
}
private isVariable(token: string): boolean {
const temp: RegExp = new RegExp("^[a-zA-Z]+$")
return temp.test(token) && !this.isFunction(token) && token !== "π";
}
private isFunction(token: string): boolean {
return ['pow', 'sin', 'cos', 'tan', 'ln', 'sqrt', 'log2', 'log10'].includes(token);
}
private getCurrentToken(): string {
return this.tokens[this.currentTokenIndex];
}
private getNextToken(): string {
this.currentTokenIndex++;
return this.getCurrentToken();
}
private parsePrimaryExpression(): number {
const token = this.getCurrentToken();
if (token === '(') {
this.getNextToken(); // consume '('
const result = this.parseExpression();
if (this.getCurrentToken() !== ')') {
throw new Error("Expected ')' after expression");
}
this.getNextToken(); // consume ')'
return result;
} else if (token === 'π') {
this.getNextToken(); // consume 'π'
return Math.PI;
} else if (this.isVariable(token)) {
if (this.existInArr(token) != -1) {
const value = this.variables[this.existInArr(token)].value;
this.getNextToken(); // consume variable
return value;
} else {
throw new Error(`Unknown variable: ${token}`);
}
} else if (this.isFunction(token)) {
return this.parseFunction();
} else {
const num = Number(token);
this.getNextToken(); // consume number
return num;
}
}
private parseFunction(): number {
const functionName = this.getCurrentToken();
this.getNextToken(); // consume function name
if (this.getCurrentToken() !== '(') {
throw new Error(`Expected '(' after function name ${functionName}`);
}
this.getNextToken(); // consume '('
const argument1 = this.parseExpression();
if (functionName === 'pow') {
if (this.getCurrentToken() !== ',') {
throw new Error(`Expected ',' between arguments for function ${functionName}`);
}
this.getNextToken(); // consume ','
const argument2 = this.parseExpression();
if (this.getCurrentToken() !== ')') {
throw new Error(`Expected ')' after arguments for function ${functionName}`);
}
this.getNextToken(); // consume ')'
return Math.pow(argument1, argument2);
}
if (this.getCurrentToken() !== ')') {
throw new Error(`Expected ')' after argument for function ${functionName}`);
}
this.getNextToken(); // consume ')'
switch (functionName) {
case 'sin':
return Math.sin(argument1);
case 'cos':
return Math.cos(argument1);
case 'tan':
return Math.tan(argument1);
case 'ln':
return Math.log(argument1);
case 'sqrt':
return Math.sqrt(argument1);
case 'log2':
return Math.log2(argument1);
case 'log10':
return Math.log10(argument1);
default:
throw new Error(`Unknown function: ${functionName}`);
}
}
private parseFactor(): number {
// 处理一元负号
if (this.getCurrentToken() === '-') {
this.getNextToken(); // consume '-'
return -this.parseFactor();
}
// 处理初级表达式
let result = this.parsePrimaryExpression();
// 处理乘法和除法
while (this.getCurrentToken() === '*' || this.getCurrentToken() === '/') {
const operator = this.getCurrentToken();
this.getNextToken(); // consume operator
const operand = this.parsePrimaryExpression();
if (operator === '*') {
result *= operand;
} else if (operator === '/') {
if (operand === 0) {
throw new Error("Division by zero");
}
result /= operand;
}
}
return result;
}
private parseExpression(): number {
let result = this.parseFactor();
while (this.getCurrentToken() === '+' || this.getCurrentToken() === '-') {
const operator = this.getCurrentToken();
this.getNextToken();
const operand = this.parseFactor();
if (operator === '+') {
result += operand;
} else if (operator === '-') {
result -= operand;
}
}
return result;
}
public evaluate(expression: string, variables: replaceableVar[] = []): number {
let temp: RegExp = new RegExp("\\s+", "g");
expression = expression.trim().replace(temp, "");
temp = new RegExp("(\\([^()]*?(?:\\([^()]*?\\)[^()]*?)*\\)|\\w+)(?:\\*\\*|\\^)(\\([^()]*?(?:\\([^()]*?\\)[^()]*?)*\\)|\\w+)", "g");
expression = expression.replace(temp, "pow($1, $2)");
this.tokens = this.tokenize(expression);
this.currentTokenIndex = 0;
this.variables = variables;
const result = this.parseExpression();
if (this.currentTokenIndex !== this.tokens.length) {
console.log("Token" + this.tokens);
console.log("当前index" + this.currentTokenIndex + " " + "当前length" + this.tokens.length);
throw new Error("Unexpected token");
}
return Number(result.toFixed(4));
}
}
export let mathjs: MathExpressionParser = new MathExpressionParser();

View File

@ -0,0 +1,48 @@
import {IPoint} from "../drawBoard/DrawPoint"
export class Exponent {
static expFunc(x: number, a: number, b: number): number {
return a * Math.exp(0.01 * x) + b;
}
/**
* 梯度下降法
* @param sampleData
* @param learningRate
* @param epochs
* @returns
*/
static gradientDescentExp(sampleData: Array<IPoint>, learningRate: number = 0.00001, epochs: number = 100000): number[] {
let a = 0.001; // 初始化参数
let b = 0.001;
for (let epoch = 0; epoch < epochs; epoch++) {
let aGrad = 0;
let bGrad = 0;
const n = sampleData.length;
for (let i = 0; i < n; i++) {
const x = sampleData[i].x;
const y = sampleData[i].y;
const yPredict = Exponent.expFunc(x, a, b);
aGrad += -2 * (y - yPredict) * Math.exp(0.01 * x);
bGrad += -2 * (y - yPredict);
}
a -= (aGrad / n) * learningRate;
b -= (bGrad / n) * learningRate;
// 检查参数是否过大或过小,若是则终止训练
if (Number.isNaN(a) || Number.isNaN(b) || !Number.isFinite(a) || !Number.isFinite(b) || Math.abs(a) > 1e10 || Math.abs(b) > 1e10) {
console.error(`Parameters became unstable at epoch ${epoch + 1}`);
break;
}
// 每1000个epoch打印一次当前参数
if (epoch % 1000 === 0) {
console.log(`Epoch ${epoch + 1}, a: ${a}, b: ${b}`);
}
}
return [a, b];
}
}

View File

@ -0,0 +1,9 @@
/**
* 拟合结果接口类型,包含拟合的函数系数和协方差矩阵
*/
export interface FitRes {
theta?: number[];
covarianceMatrix?: number[][];
error?: number;
R_squared?: number;
}

View File

@ -0,0 +1,126 @@
import {IPoint} from "../drawBoard/DrawPoint"
import {FitRes} from "./fittingResult"
/**
* 矩阵运算工具类
*/
export class Matrix {
/**
* 矩阵转置
* @param matrix
* @returns
*/
static transpose(matrix: number[][]): number[][] {
return matrix[0].map((_, colIndex) => matrix.map(row => row[colIndex]));
}
/**
* 矩阵乘法
* @param a
* @param b
* @returns
*/
static multiplyMatrices(a: number[][], b: number[][]): number[][] {
const result: number[][] = [];
for (let i = 0; i < a.length; i++) {
// result[i] = [];
result.push([]);
for (let j = 0; j < b[0].length; j++) {
let sum = 0;
for (let k = 0; k < b.length; k++) {
sum += a[i][k] * b[k][j];
}
// result[i][j] = sum;
result[i].push(sum);
}
}
return result;
}
/**
* 矩阵求逆
* @param matrix
* @returns
*/
static inverseMatrix(matrix: number[][]): number[][] {
const size = matrix.length;
const augmented = matrix.map((row, i) => [
...row,
...Array<number>(size).fill(0).map((_, j) => (i === j ? 1 : 0))
]);
for (let i = 0; i < size; i++) {
let maxRow = i;
for (let k = i + 1; k < size; k++) {
if (Math.abs(augmented[k][i]) > Math.abs(augmented[maxRow][i])) {
maxRow = k;
}
}
let temp = augmented[i];
augmented[i] = augmented[maxRow];
augmented[maxRow] = temp;
// [augmented[i], augmented[maxRow]] = [augmented[maxRow], augmented[i]];
const divisor = augmented[i][i];
for (let j = 0; j < augmented[0].length; j++) {
augmented[i][j] /= divisor;
}
for (let k = 0; k < size; k++) {
if (k !== i) {
const factor = augmented[k][i];
for (let j = 0; j < augmented[0].length; j++) {
augmented[k][j] -= factor * augmented[i][j];
}
}
}
}
return augmented.map(row => row.slice(size));
}
/**
* 最小二乘法
* @param m
* @param sampleData
* @returns
*/
static leastSquareMethod(m: number, sampleData: Array<IPoint>): FitRes {
const x = sampleData.map(d => d.x);
const y = sampleData.map(d => d.y);
const A = x.map(xi => {
const row: number[] = [];
for (let i = m; i >= 0; i--) {
row.push(Math.pow(xi, i));
}
return row;
});
const yVec = y.map(yi => [yi]);
const At = Matrix.transpose(A);
const AtA = Matrix.multiplyMatrices(At, A);
const Aty = Matrix.multiplyMatrices(At, yVec);
const invAtA = Matrix.inverseMatrix(AtA);
const theta = Matrix.multiplyMatrices(invAtA, Aty);
const residuals = yVec.map((yi, i) => [yi[0] - A[i].reduce((sum, aij, j) => sum + aij * theta[j][0], 0)]);
const residualsSquaredSum = residuals.reduce((sum, ri) => sum + ri[0] ** 2, 0);
const degreesOfFreedom = y.length - (m + 1);
const meanSquaredError = residualsSquaredSum / degreesOfFreedom;
const covarianceMatrix = invAtA.map(row => row.map(value => value * meanSquaredError));
const fitRes: FitRes = {
theta: theta.map(row => row[0]),
covarianceMatrix: covarianceMatrix
};
return fitRes;
}
}

View File

@ -0,0 +1,65 @@
import {IPoint} from "../drawBoard/DrawPoint"
import {Matrix} from "./least_square"
export class Logarithm {
static logFunc(x: number, a: number, b: number, c: number): number {
return a * Math.log(x) / Math.log(b) + c;
}
linearRegressionLog(sampleData: Array<IPoint>, base: number): number[] {
const A = sampleData.map((value) => [Math.log(value.x) / Math.log(base), 1]);
const y = sampleData.map((value) => value.y);
const At = Matrix.transpose(A);
const AtA = Matrix.multiplyMatrices(At, A);
const Aty = Matrix.multiplyMatrices(At, y.map(v => [v]));
const invAtA = Matrix.inverseMatrix(AtA);
const theta = Matrix.multiplyMatrices(invAtA, Aty);
const a: number = theta.map(row => row[0])[0];
const c: number = theta.map(row => row[0])[1];
return [ a, c ];
}
static gradientDescentLog(sampleData: Array<IPoint>, learningRate: number = 0.00001, epochs: number = 100000): number[] {
let a = 0.001; // 初始化参数
let b = 2; // 初始对数底数,需 > 0 且 ≠ 1
let c = 0.001;
const n = sampleData.length;
for (let epoch = 0; epoch < epochs; epoch++) {
let aGrad = 0;
let bGrad = 0;
let cGrad = 0;
for (let i = 0; i < n; i++) {
const x = sampleData[i].x;
const y = sampleData[i].y;
let yPredict = Logarithm.logFunc(x, a, b, c);
let logX = Math.log(x);
let logB = Math.log(b);
if (x > 0 && b > 0 && b != 1) {
yPredict = Logarithm.logFunc(x, a, b, c);
logX = Math.log(x);
logB = Math.log(b);
} else continue;
aGrad += -2 * (y - yPredict) * (logX / logB);
bGrad += -2 * (y - yPredict) * (-a * logX / (logB * logB) * (1 / b)); // 链式法则计算 b 的梯度
cGrad += -2 * (y - yPredict);
}
a -= (aGrad / n) * learningRate;
c -= (cGrad / n) * learningRate;
if (b - (bGrad / n) * learningRate <= 0) break;
else b -= (bGrad / n) * learningRate;
if (Number.isNaN(a) || Number.isNaN(b) || Number.isNaN(c)) {
console.error(`Parameters became NaN at epoch ${epoch + 1}`);
break;
}
}
return [ a, b, c ];
}
}

View File

@ -0,0 +1,425 @@
import { IPoint, PointType } from './DrawPoint';
//标准值
/**
* imageShow组件的大小(vp)
*/
export const I_WIDTH = 480;//3568 340
/**
* imageShow组件的大小(vp)
*/
export const I_HEIGHT = 250;//3568 160
/**
* 第一个X刻度对应的值
*/
const X_VALUE = 100
/**
* 第一个Y刻度对应的值
*/
const Y_VALUE = 100
export const SETTING:RenderingContextSettings = new RenderingContextSettings(true)
export const CONTEXT:CanvasRenderingContext2D = new CanvasRenderingContext2D(SETTING)
export const FIT_CONTEXT:CanvasRenderingContext2D = new CanvasRenderingContext2D(SETTING)
CONTEXT.font = '10px sans-serif'
export enum Quadrant{
ONE = 1,
FOUR = 4
}
export abstract class BoardInfo{
/**
* x刻度之间的像素距离
* 初始化后不允许修改
*/
XGapLen:number
/**
* y刻度之间的像素距离
* 初始化后不允许修改
*/
YGapLen:number
/**
* x刻度个数 (+)
* 初始化后不允许修改
*/
XCount:number
/**
* y刻度个数 (+)
* 初始化后不允许修改
*/
YCount:number
/**
* 原点的x坐标
* 初始化后不可修改
*/
X_centre:number = 0
/**
* 原点的y坐标
* 初始化后不可修改
*/
Y_centre:number = 0
/**
* canvas组件的margin (不包括箭头)
* 初始化后不可修改
*/
margin:number
/**
* 显示象限
* 初始化后不可修改
*/
quadrant:Quadrant
/**
* 画板的宽
* 初始化后不可修改
*/
width:number
/**
* 画板的高
* 初始化后不可修改
*/
height:number
//以下属性可修改
/**
* 单位X刻度对应的虚拟像素
* 根据倍数动态修正
*/
XUnit2vp:number
/**
* 单位Y刻度对应虚拟像素
* 根据倍数动态修正
*/
YUnit2vp:number
/**
* 第一个X刻度对应的值 (+)
* 根据倍数动态修正
*/
XValue:number = X_VALUE
/**
* 第一个Y刻度对应的值 (+)
* 根据倍数动态修正
*/
YValue:number = Y_VALUE
/**
* 虚拟点集
*/
virtualPoints:Array<IPoint> = []
/**
* 拟合后得到的函数的点集
*/
boardPoints:Array<IPoint> = []
constructor(margin:number,quadrant:Quadrant,Xgl:number,Ygl:number,XC:number,YC:number) {
this.margin = margin
this.quadrant = quadrant
//画板属性
this.width = I_WIDTH - 2 * this.margin
this.height = I_HEIGHT - 2 * this.margin
//定义单位刻度对应像素
this.XGapLen = Xgl
this.YGapLen = Ygl
this.XUnit2vp = Xgl/X_VALUE
this.YUnit2vp = Ygl/Y_VALUE
//定义个数
this.XCount = XC
this.YCount = YC
}
/**
* 绘制坐标轴
*/
public abstract drawAxis();
/**
* 设置画板上的xy数对
* 同时根据xy数对计算出合适的
*
* 初始化点集 需要保证xy都有赋值
*
* 返回virtualPoints
*/
public initMap(virtualPoints?:Array<IPoint>){
this.virtualPoints = []
if(virtualPoints){
this.virtualPoints = virtualPoints
}else{
let x = 0;
let y = 0
if(this.quadrant == Quadrant.ONE){
for (let index = 0; index < 20; index++) {
x = Math.random()*1000
y = Math.random()*1000
this.virtualPoints.push({x:x,y:y,pointType:PointType.CONTINUE})
}
}else{
for (let index = 0; index < 20; index++) {
x = ((Math.random()*2)-1)*500
y = ((Math.random()*2)-1)*500
this.virtualPoints.push({x:x,y:y,pointType:PointType.CONTINUE})
}
}
}
this.boardPoints = []
this.transToBoardPoints()
return this.virtualPoints
}
/**
* 绘制拟合函数
*/
public drawFitFunc(points:Array<IPoint>){
this.transToBoardPoints(points)
this.drawGraph(FIT_CONTEXT,points)
}
/**
* 绘制散点图
*/
public drawPoints(context:CanvasRenderingContext2D = CONTEXT,boardPoints:Array<IPoint> = this.boardPoints){
context.beginPath()
for (let index = 0; index < boardPoints.length; index++) {
context.fillRect(vp2px(boardPoints[index].x-1),vp2px(boardPoints[index].y-1),vp2px(1.5),vp2px(1.5))
}
context.stroke()
}
/**
* 绘制折线
*/
public drawGraph(context:CanvasRenderingContext2D = FIT_CONTEXT,boardPoints:Array<IPoint> = this.boardPoints){
FIT_CONTEXT.strokeStyle = 'red'
let isMeetDisContinue = false
let i = 0
while(boardPoints[i].pointType == PointType.DISCONTINUE && i <= boardPoints.length - 1){
i++
}
if(i == boardPoints.length){
//全间断点
return
}
if(i != 0){
context.fillRect(vp2px(boardPoints[i].x - 1),vp2px(boardPoints[i].y - 1),vp2px(1.5),vp2px(1.5))
}
context.moveTo(vp2px(boardPoints[i].x),vp2px(boardPoints[i].y))
//间断点画圆
for (let index = i + 1; index < boardPoints.length ; index++) {
if(boardPoints[index].pointType == PointType.DISCONTINUE){
isMeetDisContinue = true
continue
}
if(isMeetDisContinue){
context.moveTo(vp2px(boardPoints[index].x),vp2px(boardPoints[index].y))
context.fillRect(vp2px(boardPoints[index].x - 1),vp2px(boardPoints[index].y - 1),vp2px(1.5),vp2px(1.5))
isMeetDisContinue = false
}else{
context.lineTo(vp2px(boardPoints[index].x),vp2px(boardPoints[index].y))
}
}
context.stroke()
}
/**
* 将虚拟点集(二维)转变为画布点集
*/
protected transToBoardPoints(virtualPoints?:Array<IPoint>){
if(virtualPoints){
for (let index = 0; index < virtualPoints.length; index++) {
virtualPoints[index].x = this.getXPosition(virtualPoints[index].x)
virtualPoints[index].y = this.getYPosition(virtualPoints[index].y)
virtualPoints[index].pointType = virtualPoints[index].pointType
}
return
}
for (let index = 0; index < this.virtualPoints.length; index++) {
this.boardPoints.push({x:this.getXPosition(this.virtualPoints[index].x),y:this.getYPosition(this.virtualPoints[index].y),pointType:this.virtualPoints[index].pointType})
}
}
/**
* 将虚拟坐标转变为画布坐标x(二维)
*/
private getXPosition(x:number){
return this.X_centre + this.XUnit2vp*x
}
/**
* 将虚拟坐标转变为画布坐标x(二维)
*/
private getYPosition(y:number){
return this.Y_centre - this.YUnit2vp*y
}
}
class board4Q2D extends BoardInfo{
constructor() {
super(5,Quadrant.FOUR,43,21,5,5);//根据具体环境配置
this.X_centre = (2 * this.margin + this.width)/2
this.Y_centre = (2 * this.margin + this.height)/2
}
public drawAxis(){
CONTEXT.beginPath()
CONTEXT.moveTo(vp2px(this.margin),vp2px(this.Y_centre))
CONTEXT.lineTo(vp2px(this.width + this.margin),vp2px(this.Y_centre))
CONTEXT.lineTo(vp2px(this.width + this.margin),vp2px(this.Y_centre + 3))
CONTEXT.lineTo(vp2px(this.width + this.margin + 3),vp2px(this.Y_centre))
CONTEXT.lineTo(vp2px(this.width + this.margin),vp2px(this.Y_centre - 3))
CONTEXT.lineTo(vp2px(this.width + this.margin),vp2px(this.Y_centre))
CONTEXT.moveTo(vp2px(this.X_centre),vp2px(this.height + this.margin))
CONTEXT.lineTo(vp2px(this.X_centre),vp2px(this.margin))
CONTEXT.lineTo(vp2px(this.X_centre - 3),vp2px(this.margin))
CONTEXT.lineTo(vp2px(this.X_centre),vp2px(this.margin - 3))
CONTEXT.lineTo(vp2px(this.X_centre + 3),vp2px(this.margin))
CONTEXT.lineTo(vp2px(this.X_centre),vp2px(this.margin))
CONTEXT.fill()
CONTEXT.fillText("0",vp2px(this.X_centre + 2),vp2px(this.Y_centre + 15))
let valueX = this.X_centre - this.XValue * this.XUnit2vp * (this.XCount + 1)
let valueY = this.Y_centre + 6
let value = -this.XValue * this.XCount
let count = 2 * this.XCount
//绘制X轴
while(--count >= 0) {
valueX += this.XGapLen
if(value == 0){
value += this.XValue
valueX += this.XGapLen
}
CONTEXT.moveTo(vp2px(valueX),vp2px(this.Y_centre))
CONTEXT.lineTo(vp2px(valueX),vp2px(this.Y_centre - 5))
CONTEXT.fillText(value.toString(),vp2px(valueX - this.getXTextBias(value.toString()) - 6),vp2px(valueY + 7))
value += this.XValue
}
//绘制Y轴
valueX = this.X_centre
valueY = this.Y_centre + this.YValue * this.YUnit2vp * (this.YCount + 1)
value = -this.YValue * this.YCount
count = 2 * this.YCount
while(--count >= 0) {
valueY -= this.YGapLen
if(value == 0){
value += this.YValue
valueY -= this.YGapLen
}
CONTEXT.moveTo(vp2px(this.X_centre),vp2px(valueY))
CONTEXT.lineTo(vp2px(this.X_centre + 3),vp2px(valueY))
CONTEXT.fillText(value.toString(),vp2px(valueX - this.getYTextBias(value.toString()) + 15),vp2px(valueY + 5))
value += this.YValue
}
CONTEXT.stroke()
}
/**
* 获取修正后的X刻度对应的文本的显示横向偏移量
*/
private getXTextBias(value:string){
let bias = value.length
let flag = 1
if(value[0] == '-'){
flag = -2
}
return (bias * (bias - 1))/2 + 1 + flag * this.XGapLen/50 ;
}
/**
* 获取修正后的Y刻度对应的文本的显示横向偏移量
*/
private getYTextBias(value:string){
let bias = value.length
if(bias == 1){
return 1
}
return bias * 3.5 + 1;
}
}
class board1Q2D extends BoardInfo{
constructor() {
super(20,Quadrant.ONE,42,20,10,10);//根据具体环境配置
this.X_centre = this.margin
this.Y_centre = this.margin + this.height
}
public drawAxis(){
CONTEXT.beginPath()
CONTEXT.moveTo(vp2px(this.margin),vp2px(this.margin + this.height))
CONTEXT.lineTo(vp2px(this.margin),vp2px(this.margin))
CONTEXT.lineTo(vp2px(this.margin - 3),vp2px(this.margin))
CONTEXT.lineTo(vp2px(this.margin),vp2px(this.margin - 3))
CONTEXT.lineTo(vp2px(this.margin + 3),vp2px(this.margin))
CONTEXT.lineTo(vp2px(this.margin),vp2px(this.margin))
CONTEXT.fill()
CONTEXT.moveTo(vp2px(this.margin),vp2px(this.margin + this.height))
CONTEXT.lineTo(vp2px(this.margin + this.width),vp2px(this.margin + this.height))
CONTEXT.lineTo(vp2px(this.margin + this.width),vp2px(this.margin + this.height + 3))
CONTEXT.lineTo(vp2px(this.margin + this.width + 3),vp2px(this.margin + this.height))
CONTEXT.lineTo(vp2px(this.margin + this.width),vp2px(this.margin + this.height - 3))
CONTEXT.lineTo(vp2px(this.margin + this.width),vp2px(this.margin + this.height))
CONTEXT.fill()
CONTEXT.fillText("0",vp2px(this.X_centre - 10),vp2px(this.Y_centre + 10))
let valueX = this.X_centre
let valueY = this.Y_centre + 6
let value = this.XValue
let count = this.XCount
//x轴
while(--count >= 0) {
valueX += this.XGapLen
CONTEXT.moveTo(vp2px(valueX),vp2px(this.Y_centre))
CONTEXT.lineTo(vp2px(valueX),vp2px(this.Y_centre - 5))
CONTEXT.fillText(value.toString(),vp2px(valueX-this.getXTextBias(value.toString()) - 10),vp2px(valueY + 10))
value += this.XValue
}
//y轴
valueX = this.X_centre
valueY = this.Y_centre
value = this.YValue
count = this.YCount
while(--count >= 0) {
valueY -= this.YGapLen
if(value == 0){
value += this.YValue
valueY -= this.YGapLen
}
CONTEXT.moveTo(vp2px(this.X_centre),vp2px(valueY))
CONTEXT.lineTo(vp2px(this.X_centre + 5),vp2px(valueY))
CONTEXT.fillText(value.toString(),vp2px(valueX + 20 - this.getYTextBias(value.toString())),vp2px(valueY + 3))
value += this.YValue
}
CONTEXT.stroke()
}
/**
* 获取修正后的X刻度对应的文本的显示横向偏移量
*/
private getXTextBias(value:string){
let bias = value.length
let flag = 1
if(value[0] == '-'){
flag = -2
}
return (bias * (bias - 1))/2 + 1 + flag * this.XGapLen/50;
}
/**
* 获取修正后的Y刻度对应的文本的显示横向偏移量
*/
private getYTextBias(value:string){
let bias = value.length
if(bias == 1){
return 1
}
return bias * 3.5;
}
}
export let B4Q2D:BoardInfo = new board4Q2D()
export let B1Q2D:BoardInfo = new board1Q2D()

View File

@ -0,0 +1,15 @@
export enum PointType{
DISCONTINUE = 0,
CONTINUE = 1
}
/**
* 虚拟点集
* 根据函数关系和步长、个数直接得到的点集
*/
export interface IPoint{
x:number
y:number
pointType:PointType
}

View File

@ -0,0 +1,47 @@
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.FILE_ACCESS_MANAGER"
},
{
"name": "ohos.permission.GET_BUNDLE_INFO_PRIVILEGED"
}
],
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"orientation": "landscape",
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
]
}
}

View File

@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,5 @@
{
"src": [
"pages/Index"
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "模块描述"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

View File

@ -0,0 +1,35 @@
import hilog from '@ohos.hilog';
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function abilityTest() {
describe('ActsAbilityTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
})
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
})
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
})
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
})
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
})
})
}

View File

@ -0,0 +1,5 @@
import abilityTest from './Ability.test';
export default function testsuite() {
abilityTest();
}

View File

@ -0,0 +1,50 @@
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
import hilog from '@ohos.hilog';
import { Hypium } from '@ohos/hypium';
import testsuite from '../test/List.test';
import window from '@ohos.window';
import Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
export default class TestAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate');
hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? '');
hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? '');
let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator;
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs;
abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments();
hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!');
Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite);
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate');
windowStage.loadContent('testability/pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s',
JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy');
}
onForeground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground');
}
onBackground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground');
}
}

View File

@ -0,0 +1,17 @@
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}

View File

@ -0,0 +1,47 @@
import hilog from '@ohos.hilog';
import TestRunner from '@ohos.application.testRunner';
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
import Want from '@ohos.app.ability.Want';
let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator | undefined = undefined
let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs | undefined = undefined
async function onAbilityCreateCallback() {
hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback');
}
async function addAbilityMonitorCallback(err : Error) {
hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? '');
}
export default class OpenHarmonyTestRunner implements TestRunner {
constructor() {
}
onPrepare() {
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare ');
}
async onRun() {
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run');
abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
const bundleName = abilityDelegatorArguments.bundleName;
const testAbilityName = 'TestAbility';
let lMonitor: AbilityDelegatorRegistry.AbilityMonitor = {
abilityName: testAbilityName,
onAbilityCreate: onAbilityCreateCallback,
};
abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback)
const want: Want = {
bundleName: bundleName,
abilityName: testAbilityName
};
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
abilityDelegator.startAbility(want, (err, data) => {
hilog.info(0x0000, 'testTag', 'startAbility : err : %{public}s', JSON.stringify(err) ?? '');
hilog.info(0x0000, 'testTag', 'startAbility : data : %{public}s',JSON.stringify(data) ?? '');
})
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end');
}
}

View File

@ -0,0 +1,37 @@
{
"module": {
"name": "entry_test",
"type": "feature",
"description": "$string:module_test_desc",
"mainElement": "TestAbility",
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:test_pages",
"abilities": [
{
"name": "TestAbility",
"srcEntry": "./ets/testability/TestAbility.ets",
"description": "$string:TestAbility_desc",
"icon": "$media:icon",
"label": "$string:TestAbility_label",
"exported": true,
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"skills": [
{
"actions": [
"action.system.home"
],
"entities": [
"entity.system.home"
]
}
]
}
]
}
}

View File

@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_test_desc",
"value": "test ability description"
},
{
"name": "TestAbility_desc",
"value": "the test ability"
},
{
"name": "TestAbility_label",
"value": "test label"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,5 @@
{
"src": [
"testability/pages/Index"
]
}

View File

@ -0,0 +1,5 @@
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}

View File

@ -0,0 +1,33 @@
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest',() => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}

View File

@ -0,0 +1,18 @@
{
"hvigorVersion": "3.0.9",
"dependencies": {
"@ohos/hvigor-ohos-plugin": "3.0.9"
},
"execution": {
// "daemon": true, /* Enable daemon compilation. Default: true */
// "incremental": true, /* Enable incremental compilation. Default: true */
// "parallel": true, /* Enable parallel compilation. Default: true */
// "typeCheck": false, /* Enable typeCheck. Default: false */
},
"logging": {
// "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
},
"debugging": {
// "stacktrace": false /* Disable stacktrace compilation. Default: false */
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
import { appTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@ -0,0 +1,48 @@
#!/bin/bash
# ----------------------------------------------------------------------------
# Hvigor startup script, version 1.0.0
#
# Required ENV vars:
# ------------------
# NODE_HOME - location of a Node home dir
# or
# Add /usr/local/nodejs/bin to the PATH environment variable
# ----------------------------------------------------------------------------
HVIGOR_APP_HOME="`pwd -P`"
HVIGOR_WRAPPER_SCRIPT=${HVIGOR_APP_HOME}/hvigor/hvigor-wrapper.js
warn() {
echo ""
echo -e "\033[1;33m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
}
error() {
echo ""
echo -e "\033[1;31m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
}
fail() {
error "$@"
exit 1
}
# Determine node to start hvigor wrapper script
if [ -n "${NODE_HOME}" ];then
EXECUTABLE_NODE="${NODE_HOME}/bin/node"
if [ ! -x "$EXECUTABLE_NODE" ];then
fail "ERROR: NODE_HOME is set to an invalid directory,check $NODE_HOME\n\nPlease set NODE_HOME in your environment to the location where your nodejs installed"
fi
else
EXECUTABLE_NODE="node"
which ${EXECUTABLE_NODE} > /dev/null 2>&1 || fail "ERROR: NODE_HOME is not set and not 'node' command found in your path"
fi
# Check hvigor wrapper script
if [ ! -r "$HVIGOR_WRAPPER_SCRIPT" ];then
fail "ERROR: Couldn't find hvigor/hvigor-wrapper.js in ${HVIGOR_APP_HOME}"
fi
# start hvigor-wrapper script
exec "${EXECUTABLE_NODE}" \
"${HVIGOR_WRAPPER_SCRIPT}" "$@"

View File

@ -0,0 +1,64 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Hvigor startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
set WRAPPER_MODULE_PATH=%APP_HOME%\hvigor\hvigor-wrapper.js
set NODE_EXE=node.exe
goto start
:start
@rem Find node.exe
if defined NODE_HOME goto findNodeFromNodeHome
%NODE_EXE% --version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
echo.
echo Please set the NODE_HOME variable in your environment to match the
echo location of your NodeJs installation.
goto fail
:findNodeFromNodeHome
set NODE_HOME=%NODE_HOME:"=%
set NODE_EXE_PATH=%NODE_HOME%/%NODE_EXE%
if exist "%NODE_EXE_PATH%" goto execute
echo.
echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
echo.
echo Please set the NODE_HOME variable in your environment to match the
echo location of your NodeJs installation.
goto fail
:execute
@rem Execute hvigor
"%NODE_EXE%" "%WRAPPER_MODULE_PATH%" %*
if "%ERRORLEVEL%" == "0" goto hvigorwEnd
:fail
exit /b 1
:hvigorwEnd
if "%OS%" == "Windows_NT" endlocal
:end

View File

@ -0,0 +1,13 @@
{
"lockfileVersion": 1,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"@ohos/hypium@1.0.11": "@ohos/hypium@1.0.11"
},
"packages": {
"@ohos/hypium@1.0.11": {
"resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.11.tgz",
"integrity": "sha512-KawcLnv43C3QIYv1UbDnKCFX3MohtDxGuFvzlUxT/qf2DBilR56Ws6zrj90LdH6PjloJQwOPESuBQIHBACAK7w=="
}
}
}

View File

@ -0,0 +1,12 @@
{
"license": "",
"devDependencies": {
"@ohos/hypium": "1.0.11"
},
"author": "",
"name": "myapplication",
"description": "Please describe the basic information.",
"main": "",
"version": "1.0.0",
"dependencies": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
datafiting/figs/overall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

18
drawing/README.md Normal file
View File

@ -0,0 +1,18 @@
# drawing
### 1、项目说明
本项目拟实现一个复杂函数计算及其曲线绘制系统基本功能为1用户能够设置自变量的步长和个数2程序会根据用户设置的函数以及步长和个数信息解析函数获取点集并执行绘制的操作3用户能够自定义函数并保存在可识别函数中使其能够参与新函数的构造4用户能够切换函数分别在二维和三维环境下的绘制5程序会解析用户输入的函数将多项式分成单项式并以“树”的形式呈现。
### 2、开发环境说明
1系统版本OpenHarmony 4.0 Release
2SDK版本API 10
3Model类型Stage
4DevEco Studio版本Deveco Studio 4.0 release
5硬件环境Unionpi Whale、RK3566开发板
6预览器参数(1)DeviceType:default;(2)Resolution:560-1280;(3)DPI:240;其余参数自定。
### 3、项目主要函数调用关系图与项目输出界面演进图
图3.1 项目主要函数调用关系图
<img src="figs/relation.png" width=80% /></center>
图3.2 项目输出界面演进图
<img src="figs/evolution.png" width=80% /></center>

11
drawing/draw/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
/node_modules
/oh_modules
/local.properties
/.idea
**/build
/.hvigor
.cxx
/.clangd
/.clang-format
/.clang-tidy
**/.test

View File

@ -0,0 +1,10 @@
{
"app": {
"bundleName": "com.test.myapplication",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:app_icon",
"label": "$string:app_name"
}
}

View File

@ -0,0 +1,8 @@
{
"string": [
{
"name": "app_name",
"value": "MyApplication"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,49 @@
{
"app": {
"signingConfigs": [
{
"name": "default",
"material": {
"certpath": "C:\\Users\\cyf20\\.ohos\\config\\openharmony\\auto_ohos_default_draw_com.test.myapplication.cer",
"storePassword": "0000001A41B8AC51384118050B5A9A22C9E132AA02BE60C9CBE187B4D7B69A17D2C070E0DF83E06DC1E4",
"keyAlias": "debugKey",
"keyPassword": "0000001A98556E0863F7805DEA1D57929A5FF99B10F6509C866A4450C6198E40C8F4F0701876AB99F565",
"profile": "C:\\Users\\cyf20\\.ohos\\config\\openharmony\\auto_ohos_default_draw_com.test.myapplication.p7b",
"signAlg": "SHA256withECDSA",
"storeFile": "C:\\Users\\cyf20\\.ohos\\config\\openharmony\\auto_ohos_default_draw_com.test.myapplication.p12"
}
}
],
"products": [
{
"name": "default",
"signingConfig": "default",
"compileSdkVersion": 10,
"compatibleSdkVersion": 10,
"runtimeOS": "OpenHarmony",
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}

6
drawing/draw/entry/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test

View File

@ -0,0 +1,31 @@
{
"apiType": "stageMode",
"buildOption": {
"arkOptions": {
// "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}

View File

@ -0,0 +1,6 @@
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@ -0,0 +1,18 @@
# Define project specific obfuscation rules here.
# You can include the obfuscation configuration files in the current module's build-profile.json5.
#
# For more details, see
# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
# Obfuscation options:
# -disable-obfuscation: disable all obfuscations
# -enable-property-obfuscation: obfuscate the property names
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
# -compact: remove unnecessary blank spaces and all line feeds
# -remove-log: remove all console.* statements
# -print-namecache: print the name cache that contains the mapping from the old names to new names
# -apply-namecache: reuse the given cache file
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope

View File

@ -0,0 +1,10 @@
{
"license": "",
"devDependencies": {},
"author": "",
"name": "entry",
"description": "Please describe the basic information.",
"main": "",
"version": "1.0.0",
"dependencies": {}
}

View File

@ -0,0 +1,29 @@
@Observed
export class basicFuncInfo{
/**
* 函数名
*/
private funcName:string
/**
* 函数体
*/
private funcBody:string
constructor(funcName: string, funcBody: string) {
this.funcName = funcName
this.funcBody = funcBody
}
setFuncName(name:string){
this.funcName = name
}
setFuncBody(body:string){
this.funcBody = body
}
getFuncName(){
return this.funcName
}
getFuncBody(){
return this.funcBody
}
}

View File

@ -0,0 +1,27 @@
export class VALUE{
/**
* 栏颜色
*/
static readonly COLOR_BAR = "#EDEDED"
/**
* 字体大小(大)
*/
static readonly BIG_SIZE_FONT = 15
/**
* 字体大小(大)
*/
static readonly MIDDLE_SIZE_FONT = 12
/**
* 字体大小(小)
*/
static readonly SMALL_SIZE_FONT = 10
/**
* controlBar的背景色
*/
static readonly COLOR_COMP_CONTROLBAR_BG = "#9490D9"
/**
* controlBar的栏颜色
*/
static readonly COLOR_COMP_CONTROLBAR_BAR = "#8767F7"
}

View File

@ -0,0 +1,67 @@
import { basicFuncInfo } from '../common/BasicFuncInfo'
import { VALUE } from '../common/CommonValue'
@Component
export struct basicFunctionsBar {
@Consume('basicFuncList') basicFuncList:Array<basicFuncInfo>
build() {
Column(){
Text("可识别基本函数")
.fontSize(VALUE.BIG_SIZE_FONT)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
Row(){
Row(){
Text('函数名')
.fontSize(VALUE.SMALL_SIZE_FONT)
}.justifyContent(FlexAlign.Center).width('28%')
Blank()
Row(){
Text('函数体')
.fontSize(VALUE.SMALL_SIZE_FONT)
}.justifyContent(FlexAlign.Center).width('68%')
}.width('100%').padding({left:5,right:5})
List(){
ForEach(this.basicFuncList,(item:basicFuncInfo)=>{
ListItem(){
basicFuncItem({basicFunc:item})
}
})
}.layoutWeight(1).width('100%')
}.width('100%').height('100%')
}
}
@Component
struct basicFuncItem {
@ObjectLink basicFunc:basicFuncInfo
build() {
Row(){
Scroll(){
Row(){
Text(this.basicFunc.getFuncName())
.fontSize(VALUE.MIDDLE_SIZE_FONT)
}.justifyContent(FlexAlign.Center)
}.width('28%').scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)
Blank()
Scroll(){
Row(){
Text(this.basicFunc.getFuncBody())
.fontSize(VALUE.MIDDLE_SIZE_FONT)
}.justifyContent(FlexAlign.Center)
}.width('68%').scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)
}.width('100%').height(25)
}
}

View File

@ -0,0 +1,317 @@
import promptAction from '@ohos.promptAction'
import { basicFuncInfo } from '../common/BasicFuncInfo'
import { VALUE } from '../common/CommonValue'
import { IBoardOption, BoardType, Dimension, Quadrant } from '../utils/drawBoard/BoardType'
import { FuncType } from '../utils/drawBoard/drawPoint'
import { handle } from '../utils/handleFunc/processFunc'
@Component
export struct controlBar {
@Link inputFuncBody:string
@Link outputState:boolean
@Link boardOption:IBoardOption
build() {
Column({space:5}){
InputFunc({funcBody:$inputFuncBody,outputState:$outputState})
InputPara({boardOption:$boardOption})
}.width('100%').height('100%').backgroundColor(VALUE.COLOR_COMP_CONTROLBAR_BG).padding({top:5,bottom:5})
}
}
@Component
struct InputFunc {
@Consume isFuncChange:boolean
@Link funcBody:string
@Link outputState:boolean
@Consume('basicFuncList') basicFuncList:Array<basicFuncInfo>
@Consume dimension:Dimension
build() {
Row({space:10}){
Text('')
.width(15)
.height(30)
Stack(){
Rect().width('100%').height('100%').fill(Color.White)
TextInput({text:this.funcBody}).width('100%').height('100%').backgroundColor(Color.White).fontColor(Color.Black).fontSize(VALUE.SMALL_SIZE_FONT)
.onChange((value)=>{
this.funcBody = value
this.isFuncChange = true
})
}.height(30).backgroundColor(Color.White).layoutWeight(1)
Button('用户命名并新增基本函数')
.height(30)
.backgroundColor(Color.Yellow)
.fontColor(Color.Black)
.fontSize(VALUE.BIG_SIZE_FONT)
.width(210)
.type(ButtonType.Normal)
.onClick(()=>{
if(handle.isValidFunc(this.funcBody)) {
handle.separateFunc(this.funcBody);
let funcName = handle.funcHead;
let funcBody = handle.funcBody;
if (funcName && handle.getFuncType(this.dimension) != FuncType.NONE) {
let info: basicFuncInfo = new basicFuncInfo(funcName, funcBody)
this.basicFuncList.push(info)
}
else {
promptAction.showToast({
message: "不支持此函数类型或未为函数命名",
duration: 1500,
bottom: 20
})
}
}
})
Button('输出')
.height(30)
.backgroundColor(Color.Pink)
.fontColor(Color.Black)
.fontSize(VALUE.BIG_SIZE_FONT)
.width(70)
.type(ButtonType.Normal)
.onClick(()=>{
for (let i: number = 0; i < this.basicFuncList.length; i++) {
let standard: RegExp = new RegExp(`(^|[^a-zA-Z])${this.basicFuncList[i].getFuncName().replace('(', '\\(').replace(')', '\\)')}($|[^a-zA-Z])`);
if (standard.test(this.funcBody)) {
let index: number = this.funcBody.indexOf(this.basicFuncList[i].getFuncName());
let length: number = this.basicFuncList[i].getFuncName().length;
this.funcBody = this.funcBody.substring(0, index) + "(" + this.basicFuncList[i].getFuncBody() + ")" + this.funcBody.substring(index + length);
break;
}
}
if (handle.isValidFunc(this.funcBody)) {
if (this.dimension == Dimension.TWO) {
handle.getPoints2D();
} else {
handle.getPoints3D();
for (let i = 0; i < handle.coPot.length; i++) {
console.log(`(${handle.coPot[i].x}, ${handle.coPot[i].y}, ${handle.coPot[i].z})`);
}
}
this.outputState = !this.outputState;
}
})
}.width('100%').padding({left:10,right:10})
}
}
@Component
struct InputPara {
@Link boardOption:IBoardOption
@Consume isFuncChange:boolean
@Consume quadrant:Quadrant
@Consume dimension:Dimension
@Builder xStep(){
Row(){
Text('x步长')
.fontSize(VALUE.SMALL_SIZE_FONT)
Stack(){
Rect().width('100%').height('100%').fill(Color.White)
TextInput().width('100%').height('100%').backgroundColor(Color.White).fontColor(Color.Black).fontSize(VALUE.SMALL_SIZE_FONT)
.onChange((value)=>{
if(this.dimension == Dimension.THREE && Number(value) < 1){
WARNING("输入的x步长过小x步长最小为1")
return
}
this.boardOption.xStep = Number(value)
handle.xStep = this.boardOption.xStep;
})
}.height(30).backgroundColor(Color.White).layoutWeight(1)
}.width('100%').layoutWeight(0.5)
}
@Builder xNumber(){
Row(){
Text('x个数')
.fontSize(VALUE.SMALL_SIZE_FONT)
Stack(){
Rect().width('100%').height('100%').fill(Color.White)
TextInput().width('100%').height('100%').backgroundColor(Color.White).fontColor(Color.Black).fontSize(VALUE.SMALL_SIZE_FONT)
.onChange((value)=>{
this.boardOption.xNumber = Number(value)
handle.xCount = this.boardOption.xNumber;
})
}.height(30).backgroundColor(Color.White).layoutWeight(1)
}.width('100%').layoutWeight(0.5)
}
@Builder yStep(){
Row(){
Text('y步长')
.fontSize(VALUE.SMALL_SIZE_FONT)
Stack(){
Rect().width('100%').height('100%').fill(Color.White)
TextInput({text:this.boardOption.yStep.toString()}).width('100%').height('100%').backgroundColor(Color.White).fontColor(Color.Black).fontSize(VALUE.SMALL_SIZE_FONT)
.onChange((value)=>{
this.boardOption.yStep = Number(value)
handle.yStep = this.boardOption.yStep;
})
}.height(30).backgroundColor(Color.White).layoutWeight(1)
}.width('100%').layoutWeight(0.5)
}
@Builder quadrantSwitch(quadrant:Quadrant){
Row(){
Radio({value:quadrant.toString(),group:"Quadrant"})
.checked(this.quadrant == quadrant)
.onClick(()=>{
this.quadrant = quadrant
this.isFuncChange = true
if(quadrant == Quadrant.FOUR){
handle.setEdge(-10, 10);
}else{
handle.setEdge(0, 20);
}
handle.updateEdge(this.boardOption.xAmplification);
})
Text(quadrant == Quadrant.ONE?"一象限":"四象限")
.fontSize(VALUE.SMALL_SIZE_FONT)
}.width('100%').height('50%').alignItems(VerticalAlign.Center).justifyContent(FlexAlign.Center)
}
@Builder dimensionSwitch(dimension:Dimension){
Row(){
Radio({value:dimension.toString(),group:"TwoDimensional"})
.checked(this.dimension == dimension)
.onClick(()=>{
this.boardOption = {
xStep:0.1,
xNumber:0,//
yStep:1,
yNumber:0,
xAmplification:1,
yAmplification:1,
zAmplification:1
}
this.dimension = dimension
this.quadrant = Quadrant.FOUR
this.isFuncChange = true
if(dimension == Dimension.TWO){
handle.setEdge(-10, 10);
handle.updateEdge(this.boardOption.xAmplification);
}else{
handle.setEdge(-5, 5, 10);
handle.updateEdge(this.boardOption.xAmplification, this.boardOption.yAmplification)
}
})
Text(dimension == Dimension.TWO?"二维":"三维")
.fontSize(VALUE.SMALL_SIZE_FONT)
}.width('100%').height('50%').alignItems(VerticalAlign.Center).justifyContent(FlexAlign.Center)
}
@Builder xAmplification(){
Row(){
Text('x放大倍数')
.fontSize(VALUE.SMALL_SIZE_FONT)
Stack(){
Rect().width('100%').height('100%').fill(Color.White)
TextInput({text:this.boardOption.xAmplification.toString()}).width('100%').height('100%').backgroundColor(Color.White).fontColor(Color.Black).fontSize(VALUE.SMALL_SIZE_FONT)
.onChange((value)=>{
this.boardOption.xAmplification = Number(value)
handle.updateEdge(this.boardOption.xAmplification);
})
}.height(30).backgroundColor(Color.White).layoutWeight(1)
}.width('100%').layoutWeight(0.5)
}
@Builder yAmplification(){
Row(){
Text('y放大倍数')
.fontSize(VALUE.SMALL_SIZE_FONT)
Stack(){
Rect().width('100%').height('100%').fill(Color.White)
TextInput({text:this.boardOption.yAmplification.toString()}).width('100%').height('100%').backgroundColor(Color.White).fontColor(Color.Black).fontSize(VALUE.SMALL_SIZE_FONT)
.onChange((value)=>{
this.boardOption.yAmplification = Number(value)
})
}.height(30).backgroundColor(Color.White).layoutWeight(1)
}.width('100%').layoutWeight(0.5)
}
@Builder zAmplification(){
Row(){
Text('z放大倍数')
.fontSize(VALUE.SMALL_SIZE_FONT)
Stack(){
TextInput({text:this.boardOption.zAmplification.toString()}).width('100%').height('100%').backgroundColor(Color.White).fontColor(Color.Black).fontSize(VALUE.SMALL_SIZE_FONT)
.onChange((value)=>{
this.boardOption.zAmplification = Number(value)
})
.fontSize(10)
}.height(30).backgroundColor(Color.White).layoutWeight(1)
}.width('100%')
}
build() {
Row({space:10}){
Column({space:5}){
this.xStep()
Blank()
if (this.dimension == Dimension.TWO){
this.xNumber()
}else{
this.yStep()
}
}.width('24%').height('100%').backgroundColor(VALUE.COLOR_COMP_CONTROLBAR_BAR)
if(this.dimension == Dimension.TWO){
Row(){
Column(){
Text("坐标轴")
.fontSize(VALUE.BIG_SIZE_FONT)
}.height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).width('40%')
Column(){
this.quadrantSwitch(Quadrant.FOUR)
this.quadrantSwitch(Quadrant.ONE)
}.height('100%').width('60%')
}.width('22%').height('100%').backgroundColor(VALUE.COLOR_COMP_CONTROLBAR_BAR)
}else{
Column(){
this.xAmplification()
Blank()
this.yAmplification()
}.width('22%').height('100%').backgroundColor(VALUE.COLOR_COMP_CONTROLBAR_BAR)
}
Row(){
Column(){
Text("维度")
.fontSize(VALUE.BIG_SIZE_FONT)
}.height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).width('40%')
Column(){
this.dimensionSwitch(Dimension.TWO)
this.dimensionSwitch(Dimension.THREE)
}.height('100%').width('60%')
}.width('22%').height('100%').backgroundColor(VALUE.COLOR_COMP_CONTROLBAR_BAR)
Column({space:5}){
if(this.dimension == Dimension.TWO){
this.xAmplification()
Blank()
this.yAmplification()
}else{
this.zAmplification()
}
}.width('28%').height('100%').backgroundColor(VALUE.COLOR_COMP_CONTROLBAR_BAR)
}.width('100%').layoutWeight(1).padding({left:10,right:10})
}
}
let WARNING = (message:string,duration?:number)=>{
promptAction.showToast({
message:message,
duration:duration??2000
})
}

View File

@ -0,0 +1,37 @@
import { handle } from '../utils/handleFunc/processFunc'
@Component
export struct functionParsingDiagram {
@Link @Watch('initNodeTree') outputState: boolean
@Link func: string
@State nodeTree: Array<Array<string>> = []
initNodeTree() {
this.nodeTree = handle.createNodeTree(this.func);
}
build() {
Column(){
if (this.nodeTree) {
ForEach(this.nodeTree, (item: string[]) => {
Row() {
ForEach(item, (node: string) => {
Scroll() {
Text(node)
.textAlign(TextAlign.Center)
.fontSize(12)
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.layoutWeight(node.length < 8 ? 1 : 2)
.borderWidth(1)
.borderColor(Color.Black)
.margin(1)
})
}
.size({ width: "100%", height: 25 })
})
}
}.justifyContent(FlexAlign.Start).alignItems(HorizontalAlign.Center).width('100%').height('100%').padding(5)
}
}

View File

@ -0,0 +1,25 @@
import { AREA_CONTEXT, BoardInfo, CONTEXT, Dimension, } from '../utils/drawBoard/BoardType'
@Component
export struct imageShow {
@Prop board:BoardInfo
@Prop loop1:string
@Prop loop2:string
build() {
Stack(){
Canvas(CONTEXT).width("100%").height(this.loop2)
.onReady(()=>{
this.board.drawAxis()
if (this.board.dimension == Dimension.TWO) {
this.board.drawGraph()
}
})
Canvas(AREA_CONTEXT).width("100%").height(this.loop1)
.onReady(()=>{
if (this.board.dimension == Dimension.THREE) {
this.board.drawGraph()
}
})
}.width('100%').height('100%')
}
}

View File

@ -0,0 +1,44 @@
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}

View File

@ -0,0 +1,187 @@
import { basicFuncInfo } from '../common/BasicFuncInfo';
import { basicFunctionsBar } from '../component/BasicFunctionsBar';
import { controlBar } from '../component/ControlBar';
import { functionParsingDiagram } from '../component/FunctionParsingDiagram';
import { imageShow } from '../component/ImageShow';
import { B1Q2D, B4Q2D, B4Q3D, BoardInfo, IBoardOption, Dimension, Quadrant } from '../utils/drawBoard/BoardType';
import { FuncType, IVirtualPoints, PointType } from '../utils/drawBoard/drawPoint';
import { handle } from '../utils/handleFunc/processFunc'
@Entry
@Component
struct Index {
@Provide('basicFuncList') basicFuncList:Array<basicFuncInfo> = []
//输入的函数
@State inputFunction:string = ""
//是否把输入的函数输出成图像
@State @Watch('initBoard')outputState:boolean = false
//函数是否更改
@Provide isFuncChange:boolean = false
@State loop1:string = '100%'
@State loop2:string = '100%'
//board
@State board:BoardInfo|null = null
@Provide quadrant:Quadrant = Quadrant.FOUR
@Provide dimension:Dimension = Dimension.TWO
//boardOption
//默认的option
@State boardOption:IBoardOption = {
xStep:0.1,
xNumber:0,//
yStep:1,
yNumber:0,
xAmplification:1,
yAmplification:1,
zAmplification:1
}
aboutToAppear() {
handle.setEdge(-10, 10);
handle.updateEdge(this.boardOption.xAmplification);
}
/**
* 初始化board
*/
initBoard(){
this.createBoard()
//二维测试样例
//三维测试样例
// 1.YC y=3
// point.points.push({y:3,pointType:PointType.CONTINUE})
// point.funcType = FuncType.YC
//2.XC x=3
// point.points.push({x:3,pointType:PointType.CONTINUE})
// point.funcType = FuncType.XC
//3.ZC z=3
// point.points.push({z:3,pointType:PointType.CONTINUE})
// point.funcType = FuncType.ZC
//4.YX y=x^2 [-5,5]
// let c=-5
// while(c <= 5){
// point.points.push({x:c,y:-c*c,pointType:PointType.CONTINUE})
// c+=0.1;
// }
// point.funcType = FuncType.YX
//5.YX x=y^2
// let c = -5
// while(c <= 5){
// point.points.push({x:-c*c,y:c,pointType:PointType.CONTINUE})
// c+=1;
// }
// point.funcType = FuncType.YX
//6.ZX z=x^2
// let c=-5
// while(c <= 5){
// point.points.push({x:c,z:c*c,pointType:PointType.CONTINUE})
// c+=1;
// }
// point.funcType = FuncType.ZX
//6.ZY z=y^2
// let c=-5
// while(c <= 5){
// point.points.push({y:c,z:c*c,pointType:PointType.CONTINUE})
// c+=1;
// }
// point.funcType = FuncType.ZY
//7.ZXY z= x^2 + y^2 需要先调整步长 否则报错
// let dx = -3
// let dy = -3
// for(let index = 0;index < 6;index++){//this.boardOption.xNumber
// for (let index = 0; index < 6; index++) {//this.boardOption.yNumber
// point.points.push({x:dx,y:dy,z:-(dx*dx + dy*dy),pointType:PointType.CONTINUE})
// dy+=1
// }
// dx+=1;
// dy=-3
// }
// point.funcType = FuncType.ZXY
// //获取到点集
let point:IVirtualPoints = {
points: handle.coPot,
funcType: handle.getFuncType(this.dimension)
}
console.log('testTag','points_len:',point.points.length)
console.log('testTag','points_funcType:',point.funcType)
//初始化画板下的需要绘制的坐标点
if(this.isFuncChange){
if(this.board != null){
this.boardOption = this.board.initMap(point)
}
this.isFuncChange = false
}else{
if (this.board != null) {
this.board.setOption(this.boardOption,point)
}
}
//刷新canvas组件
if(this.loop1 == '100%'){
this.loop1 = '101%'
}else{
this.loop1 = '100%'
}
if(this.loop2 == '100%'){
this.loop2 = '101%'
}else{
this.loop2 = '100%'
}
}
build() {
Column(){
Row(){
if(this.board != null){
imageShow({loop1:this.loop1,loop2:this.loop2,board:this.board})
.width('50%')
.height('100%')
.backgroundColor('#F5F5F5')
}else{
Text('')
.width('50%')
.height('100%')
.backgroundColor('#F5F5F5')
}
functionParsingDiagram({ outputState: this.outputState, func: this.inputFunction })
.width('25%')
.height('100%')
.backgroundColor('#F5F5F5')
basicFunctionsBar()
.width('25%')
.height('100%')
.backgroundColor(Color.Pink)
}.width('100%').layoutWeight(1)
controlBar({inputFuncBody:$inputFunction,outputState:$outputState,boardOption:$boardOption}).width('100%').height('30%')
}.width('100%').height('100%')
}
/**
* 创建board
*/
createBoard(){
if (this.dimension == Dimension.TWO){
if(this.quadrant == Quadrant.ONE){
B1Q2D.setOption(this.boardOption)
this.board = B1Q2D
}
if(this.quadrant == Quadrant.FOUR){
B4Q2D.setOption(this.boardOption)
this.board = B4Q2D
}
}
if(this.dimension == Dimension.THREE){
B4Q3D.setOption(this.boardOption)
this.board = B4Q3D
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
export enum PointType{
DISCONTINUE = 0,
CONTINUE = 1,
}
export enum FuncType{
/**
* 未选择
*/
NONE = -1,
/**
* y = c型
* 为此枚举值时 只能填y的值且其为c的值 且虚拟点集长度为1 pointType字段必为CONTINUE
*/
YC = 0,
/**
* x = c型
* 为此枚举值时 只能填x的值且其为c的值 且虚拟点集长度为1 pointType字段必为CONTINUE
*/
XC = 1,
/*
* y = f(x)型
* 为此枚举值时 虚拟点集中只能填xy的值
*/
YX = 2,
/**
* z = c型
* 为此枚举值时 只能填z的值且其为c的值 且虚拟点集长度为1 pointType字段必为CONTINUE
*/
ZC = 3,
/**
* z = f(x)型
* 为此枚举值时 虚拟点集中只能填zx的值
*/
ZX = 4,
/**
* z = f(y)型
* 为此枚举值时 虚拟点集中只能填zy的值
*/
ZY = 5,
/**
* z = f(x,y)型
* 为此枚举值时 虚拟点集中只能填zxy的值
* 且必须为边界值
*
*/
ZXY = 6
}
/**
* 虚拟点集
* 根据函数关系和步长、个数直接得到的点集
*/
export interface IPoint{
x?:number
y?:number
z?:number
pointType:PointType
}
export interface IVirtualPoints{
points:Array<IPoint>
funcType:FuncType
}
/**
* 画板点集
* 以屏幕左上角为原点的平面坐标系下的点集
*/
export interface IBoardPoint{
x:number
y:number
pointType:PointType
}

View File

@ -0,0 +1,361 @@
interface replaceableVar {
char: string,
value: number
}
class MathExpressionParser {
private tokens: string[];
private currentTokenIndex: number;
private variables: replaceableVar[];
private nodeTree: string[][];
private exp: string;
private currentExpIndex: number;
private layer: number;
constructor() {
this.tokens = []
this.currentTokenIndex = 0
this.variables = []
// this.nodeTree = new Array(50).fill([])
this.nodeTree = [];
for (let i = 0; i < 50; i++) {
this.nodeTree.push([]);
}
this.exp = ""
this.currentExpIndex = 0
this.layer = 0
}
private tokenize(expression: string): string[] {
const regex: RegExp = new RegExp("(\\d+\\.?\\d*|\\.\\d+|[a-zA-Z]+|\\S)", "g");
const originalTokens = expression.match(regex) || [];
const Tokens: string[] = [];
for (let i = 0; i < originalTokens.length; i++) {
Tokens.push(originalTokens[i]);
if (i < originalTokens.length - 1) {
const currentToken = originalTokens[i];
const nextToken = originalTokens[i + 1];
if ((this.isNumber(currentToken) && this.isVariable(nextToken)) ||
(this.isVariable(currentToken) && this.isVariable(nextToken)) ||
(currentToken === ')' && (this.isVariable(nextToken) || this.isNumber(nextToken))) ||
(this.isNumber(currentToken) && nextToken === '(') ||
(this.isVariable(currentToken) && nextToken === '(')) {
Tokens.push('*');
}
}
}
return Tokens;
}
private existInArr(token: string): number {
for (let i: number = 0; i < this.variables.length; i++) {
if (this.variables[i].char == token) {
return i;
}
}
return -1;
}
private isNumber(token: string): boolean {
const num = Number(token);
return !Number.isNaN(num) && Number.isFinite(num);
}
private isVariable(token: string): boolean {
const temp: RegExp = new RegExp("^[a-zA-Z]+$")
return temp.test(token) && !this.isFunction(token) && token !== "π";
}
private isFunction(token: string): boolean {
return ['pow', 'sin', 'cos', 'tan', 'ln', 'sqrt', 'log2', 'log10'].includes(token);
}
private getCurrentToken(): string {
return this.tokens[this.currentTokenIndex];
}
private getNextToken(): string {
this.currentTokenIndex++;
return this.getCurrentToken();
}
private parsePrimaryExpression(): number {
const token = this.getCurrentToken();
if (token === '(') {
this.getNextToken(); // consume '('
const result = this.parseExpression();
if (this.getCurrentToken() !== ')') {
throw new Error("Expected ')' after expression");
}
this.getNextToken(); // consume ')'
return result;
} else if (token === 'π') {
this.getNextToken(); // consume 'π'
return Math.PI;
} else if (this.isVariable(token)) {
if (this.existInArr(token) != -1) {
const value = this.variables[this.existInArr(token)].value;
this.getNextToken(); // consume variable
return value;
} else {
throw new Error(`Unknown variable: ${token}`);
}
} else if (this.isFunction(token)) {
return this.parseFunction();
} else {
const num = Number(token);
this.getNextToken(); // consume number
return num;
}
}
private parseFunction(): number {
const functionName = this.getCurrentToken();
this.getNextToken(); // consume function name
if (this.getCurrentToken() !== '(') {
throw new Error(`Expected '(' after function name ${functionName}`);
}
this.getNextToken(); // consume '('
const argument1 = this.parseExpression();
if (functionName === 'pow') {
if (this.getCurrentToken() !== ',') {
throw new Error(`Expected ',' between arguments for function ${functionName}`);
}
this.getNextToken(); // consume ','
const argument2 = this.parseExpression();
if (this.getCurrentToken() !== ')') {
throw new Error(`Expected ')' after arguments for function ${functionName}`);
}
this.getNextToken(); // consume ')'
return Math.pow(argument1, argument2);
}
if (this.getCurrentToken() !== ')') {
throw new Error(`Expected ')' after argument for function ${functionName}`);
}
this.getNextToken(); // consume ')'
switch (functionName) {
case 'sin':
return Math.sin(argument1);
case 'cos':
return Math.cos(argument1);
case 'tan':
return Math.tan(argument1);
case 'ln':
return Math.log(argument1);
case 'sqrt':
return Math.sqrt(argument1);
case 'log2':
return Math.log2(argument1);
case 'log10':
return Math.log10(argument1);
default:
throw new Error(`Unknown function: ${functionName}`);
}
}
private parseFactor(): number {
// 处理一元负号
if (this.getCurrentToken() === '-') {
this.getNextToken(); // consume '-'
return -this.parseFactor();
}
// 处理初级表达式
let result = this.parsePrimaryExpression();
// 处理乘法和除法
while (this.getCurrentToken() === '*' || this.getCurrentToken() === '/') {
const operator = this.getCurrentToken();
this.getNextToken(); // consume operator
const operand = this.parsePrimaryExpression();
if (operator === '*') {
result *= operand;
} else if (operator === '/') {
if (operand === 0) {
throw new Error("Division by zero");
}
result /= operand;
}
}
return result;
}
private parseExpression(): number {
let result = this.parseFactor();
while (this.getCurrentToken() === '+' || this.getCurrentToken() === '-') {
const operator = this.getCurrentToken();
this.getNextToken();
const operand = this.parseFactor();
if (operator === '+') {
result += operand;
} else if (operator === '-') {
result -= operand;
}
}
return result;
}
public evaluate(expression: string, variables: replaceableVar[] = []): number {
this.tokens = this.tokenize(expression);
this.currentTokenIndex = 0;
this.variables = variables;
const result = this.parseExpression();
if (this.currentTokenIndex !== this.tokens.length) {
console.log("Token" + this.tokens);
console.log("当前index" + this.currentTokenIndex + " " + "当前length" + this.tokens.length);
throw new Error("Unexpected token");
}
return Number(result.toFixed(4));
}
private getCurrentChar(): string {
return this.exp[this.currentExpIndex];
}
private getNextChar(): string {
this.currentExpIndex++;
return this.getCurrentChar();
}
private recursivePartitioning(expression: string) {
this.exp = "";
this.tokens = [];
this.tokens = this.tokenize(expression);
for (let i = 0; i < this.tokens.length; i++) {
this.exp += this.tokens[i];
}
console.log(this.exp + "----" + this.layer)
let temp: RegExp = new RegExp("^\\-?[a-zA-Z0-9]+$");
if (temp.test(this.exp)) return;
this.currentExpIndex = 0;
let parenthesizedForms: Array<string> = [];
let leftIndex: number = 0;
let rightIndex: number = 0;
let char: string = "$";
while (this.currentExpIndex < this.exp.length) {
if (this.getCurrentChar() == '(') {
parenthesizedForms.push("");
let count: number = parenthesizedForms.length - 1;
let leftCount: number = 1;
let rightCount: number = 0;
this.getNextChar();
leftIndex = this.currentExpIndex;
let sign: number = 0;
while (true) {
if (this.getCurrentChar() == '(') leftCount++;
if (this.getCurrentChar() == ')') rightCount++;
if (leftCount == rightCount) {
rightIndex = this.currentExpIndex;
break;
}
parenthesizedForms[count] += this.getCurrentChar();
this.getNextChar();
sign++;
if (sign > 1000) {
console.log("leftCount: " + leftCount + " " + "rightCount: " + rightCount);
this.exp = "";
this.tokens = [];
this.nodeTree = [];
this.layer = 0;
this.currentExpIndex = 0;
throw new Error("The number of brackets does not match");
}
}
let repeatNum: number = parenthesizedForms[count].length - count.toString().length;
if (repeatNum >= 0) {
this.exp = this.exp.substring(0, leftIndex) + count.toString() + char.repeat(repeatNum) + this.exp.substring(rightIndex);
} else {
throw new Error("Functions with so many parentheses are not supported");
}
leftIndex = rightIndex = 0;
}
this.getNextChar();
}
let nail: number = this.nodeTree[this.layer].length;
let symbolParts: RegExpMatchArray | null = null;
let includesAS: RegExp = new RegExp("(?<=\\d|[a-zA-Z]|\\))[+-](?=\\d|[a-zA-Z]|\\()", "g");
let includesMD: RegExp = new RegExp("(?<=\\d|[a-zA-Z]|\\))[*/](?=\\d|[a-zA-Z]|\\()", "g");
let includesPow: RegExp = new RegExp("(?<=\\d|[a-zA-Z]|\\))(?:[*]{2}|[\\^])(?=\\d|[a-zA-Z]|\\()", "g");
if (includesAS.test(this.exp)) {
this.nodeTree[this.layer] = this.nodeTree[this.layer].concat(this.exp.split(includesAS));
symbolParts = this.exp.match(includesAS);
} else if (includesMD.test(this.exp)) {
this.nodeTree[this.layer] = this.nodeTree[this.layer].concat(this.exp.split(includesMD));
} else if (includesPow.test(this.exp)) {
this.nodeTree[this.layer] = this.nodeTree[this.layer].concat(this.exp.split(includesPow));
} else {
if (this.exp[0] != "(") {
let label = 0;
for (let i = 0; i < this.exp.length; i++) {
if (this.exp[i] == "(") {
label = i;
break;
}
}
this.exp = this.exp.substring(label);
}
this.nodeTree[this.layer].push(this.exp);
}
if (symbolParts) {
symbolParts.forEach((match, index) => {
if (match == "-") {
this.nodeTree[this.layer][index + 1] = "-".concat(this.nodeTree[this.layer][index + 1]);
}
})
}
// for (let i = nail; i < this.nodeTree[this.layer].length; i++) {
// console.log(this.nodeTree[this.layer][i]);
// }
// for (let i: number = 0; i < parenthesizedForms.length; i++) {
// console.log("需替换字符" + parenthesizedForms[i] + "" + i + " " + this.layer)
// }
for (let i = nail; i < this.nodeTree[this.layer].length; i++) {
for (let j = 0; j < this.nodeTree[this.layer][i].length; j++) {
if (this.nodeTree[this.layer][i][j] == "(") {
let standard: RegExp = new RegExp("(?<=\\d|[a-zA-Z]|\\))([+-]|[*/]|[*]{2}|[\\^])(?=\\d|[a-zA-Z]|\\()", "g");
let flag: Boolean = false;
if (j == 0 && !standard.test(this.nodeTree[this.layer][i])) {
flag = true;
}
let start = j + 1;
let storeIndex = Number(this.nodeTree[this.layer][i][start]);
this.nodeTree[this.layer][i] = this.nodeTree[this.layer][i].substring(0, start) + parenthesizedForms[storeIndex] + this.nodeTree[this.layer][i].substring(start + parenthesizedForms[storeIndex].length);
if (flag) this.nodeTree[this.layer][i] = this.nodeTree[this.layer][i].substring(1, this.nodeTree[this.layer][i].length - 1);
// break;
j += parenthesizedForms[storeIndex].length + 2;
}
}
}
// for (let i = nail; i < this.nodeTree[this.layer].length; i++) {
// console.log(this.layer + " " + this.nodeTree[this.layer][i]);
// }
for (let i = nail; i < this.nodeTree[this.layer].length; i++) {
this.layer++;
this.recursivePartitioning(this.nodeTree[this.layer - 1][i]);
this.layer--;
}
}
createNode(func: string) {
// this.nodeTree.length = 0;
// this.nodeTree = new Array(50).fill([]);
this.nodeTree = [];
for (let i = 0; i < 50; i++) {
this.nodeTree.push([]);
}
this.recursivePartitioning(func);
return this.nodeTree;
}
}
export let mathjs: MathExpressionParser = new MathExpressionParser();

View File

@ -0,0 +1,399 @@
import promptAction from '@ohos.promptAction';
import { BusinessError } from '@ohos.base';
import { mathjs } from '../handleFunc/analysis'
import { PointType, IPoint, FuncType } from '../drawBoard/drawPoint'
import { Dimension } from '../drawBoard/BoardType'
export class ProcessFunc {
private funcId: string; // 函数(用以查询坐标轴进而确定函数类型)
private funcText: string; // 函数体(经过一定符号转换处理,用以计算)
xMin: number; // x轴最小整数值
xMax: number; // y轴最大整数值
private yMin: number; // x轴最小整数值
private yMax: number; // y轴最大整数值
private xMinDefault: number;
private xMaxDefault: number;
private yMinDefault: number;
private yMaxDefault: number;
nodeTree: Array<Array<string>> // 节点树数组
coPot: Array<IPoint>; // 存储获取的坐标点
funcHead: string; // 存储函数头
funcBody: string; // 存储函数体
xCount: number; // 取x点个数
yCount: number; // 取y点个数
xStep: number; // x轴步长值决定每个点取点间隔
yStep: number; // y轴步长值决定每个点取点间隔
row: number; // 三维取点点集转为二维数组的行数
col: number; // 三维取点点集转为二维数组的列数
constructor() {
this.nodeTree = [];
this.coPot = [];
this.funcId = this.funcText = this.funcHead = this.funcBody = "";
this.xStep = this.yStep = 0.1;
this.xCount = this.yCount = 0;
this.xMin = this.xMax = this.yMin = this.yMax = 0;
this.xMinDefault = this.xMaxDefault = this.yMinDefault = this.yMaxDefault = 0;
this.row = this.col = 0;
}
/**
* 对promptAction.showToast()的封装。默认持续1.5s距离底部50vp
* @param message 弹窗提示的文本信息
*/
private showToast(message: string) {
try {
promptAction.showToast({
message: message,
duration: 1500,
bottom: 20
});
} catch (error) {
let message = (error as BusinessError).message
let code = (error as BusinessError).code
console.error(`showToast args error code is ${code}, message is ${message}`);
}
}
/**
* 初始化默认的坐标轴最大最小边界点
* @param xMin
* @param xMax
* @param yMin
* @param yMax
*/
setEdge(xMin: number, xMax: number, y?: number, ) {
this.xMinDefault = xMin;
this.xMaxDefault = xMax;
if (y) {
this.yMinDefault = -y;
this.yMaxDefault = y;
}
this.updateEdge(1);
}
/**
* 根据放大倍数更新边缘点
* @param xTimes
* @param yTimes
*/
updateEdge(xTimes: number, yTimes?: number) {
this.xMin = this.xMinDefault * xTimes;
this.xMax = this.xMaxDefault * xTimes;
if (yTimes) {
this.yMin = this.yMinDefault * yTimes;
this.yMax = this.yMaxDefault * yTimes;
}
}
/**
* 判断输入的数学表达式是否合法,能够运算则合法,抛出错误则不合法
* @param func 数学表达式
* @returns
*/
isValidFunc(func: string): boolean {
this.funcId = this.funcText = "";
let temp: RegExp = new RegExp("\\s+", "g");
func = func.trim().replace(temp, "");
// temp = new RegExp("(\\(([^()]+)\\)|\\w+)(?:\\*\\*|\\^)(\\(([^()]+)\\)|\\w+)", "g");
temp = new RegExp("(\\([^()]*?(?:\\([^()]*?\\)[^()]*?)*\\)|\\w+)(?:\\*\\*|\\^)(\\([^()]*?(?:\\([^()]*?\\)[^()]*?)*\\)|\\w+)", "g");
func = func.replace(temp, "pow($1, $2)");
if (func.includes("=") && func.split("=").length === 2) {
this.funcId = func.split("=")[0];
this.funcText = func.split("=")[1];
} else {
this.funcId = "";
this.funcText = func;
}
try {
let testX: number = 1;
let testY: number = 1;
let uselessRes: number = mathjs.evaluate(this.funcText, [{ char: "x", value: testX }, { char: "y", value: testY }]);
if (!Number.isNaN(uselessRes)) return true;
else return false;
} catch (err) {
console.log("错误的函数格式为:" + this.funcText)
this.showToast("函数错误!" + err);
return false;
}
}
/**
* 分离函数头和函数体
* @param func
*/
separateFunc(func: string) {
this.funcHead = this.funcBody = "";
let temp: RegExp = new RegExp("\\s+", "g");
func = func.trim().replace(temp, "");
if (func.includes("=") && func.split("=").length === 2) {
this.funcHead = func.split("=")[0];
this.funcBody = func.split("=")[1];
} else {
this.funcHead = "";
this.funcBody = func;
}
}
/**
* 获取函数类型
* @param dimension 当前的维度
* @returns
*/
getFuncType(dimension: Dimension): FuncType {
if (dimension === Dimension.TWO) {
if (!this.funcId) {
if (Number.isNaN(Number(this.funcText))) return FuncType.YX;
else this.showToast("请输入正确的函数格式");
}
if (this.funcId && this.funcId != "x") {
if (Number.isNaN(Number(this.funcText))) return FuncType.YX;
else return FuncType.YC;
}
else if (!Number.isNaN(Number(this.funcText))) return FuncType.XC;
else this.showToast("函数命名不可以与自变量同名!");
} else if (dimension === Dimension.THREE) {
let standard_x: RegExp = new RegExp("(^|[^a-zA-Z])x($|[^a-zA-Z])");
let standard_y: RegExp = new RegExp("(^|[^a-zA-Z])y($|[^a-zA-Z])");
if (!this.funcId) {
if (standard_x.test(this.funcText) && standard_y.test(this.funcText)) return FuncType.ZXY;
else if (standard_x.test(this.funcText) && !standard_y.test(this.funcText)) return FuncType.ZX;
else if (!standard_x.test(this.funcText) && standard_y.test(this.funcText)) return FuncType.ZY;
else this.showToast("请输入正确的函数格式");
}
if (this.funcId && this.funcId != "x" && this.funcId != "y") {
if (!Number.isNaN(Number(this.funcText))) return FuncType.ZC;
else if (standard_x.test(this.funcText) && standard_y.test(this.funcText)) return FuncType.ZXY;
else if (standard_x.test(this.funcText) && !standard_y.test(this.funcText)) return FuncType.ZX;
else if (!standard_x.test(this.funcText) && standard_y.test(this.funcText)) return FuncType.ZY;
}
else if (this.funcId == "x" && !Number.isNaN(Number(this.funcText))) return FuncType.XC;
else if (this.funcId == "y" && !Number.isNaN(Number(this.funcText))) return FuncType.YC;
else this.showToast("没有YX或XY类型函数||函数命名不可与自变量同名!");
}
return FuncType.NONE;
}
/**
* 判断当前点是否连续
* @param xValue 当前x值
* @param yValue 当前y值
* @param zValue 当前z值
* @param epsilon 模拟的逼近无限小值
* @param threshold 模拟的逼近无限大值
* @returns 返回一个布尔值true则表示此点连续false表示不连续
*/
private judgeContinue(xValue: number, yValue: number, zValue?: number, epsilon: number = 1e-10, threshold: number = 1e5): boolean {
if (Number.isNaN(yValue) || Number.isNaN(zValue)) {
return false;
}
let leftLimitX: number = 0;
let rightLimitX: number = 0;
let leftLimitY: number = 0;
let rightLimitY: number = 0;
let standard: RegExp = new RegExp("(^|[^a-zA-Z])y($|[^a-zA-Z])")
try {
if (!standard.test(this.funcText)) {
leftLimitX = mathjs.evaluate(this.funcText, [{ char: "x", value: xValue - epsilon }]);
rightLimitX = mathjs.evaluate(this.funcText, [{ char: "x", value: xValue + epsilon }]);
if (Math.abs(rightLimitX - leftLimitX) > 10 * epsilon) {
return false;
}
if (Math.abs(leftLimitX - yValue) > 10 * epsilon || Math.abs(rightLimitX - yValue) > 10 * epsilon) {
return false;
}
} else {
leftLimitX = mathjs.evaluate(this.funcText, [{ char: "x", value: xValue - epsilon }, { char: "y", value: yValue }]);
rightLimitX = mathjs.evaluate(this.funcText, [{ char: "x", value: xValue + epsilon }, { char: "y", value: yValue }]);
leftLimitX = mathjs.evaluate(this.funcText, [{ char: "x", value: xValue }, { char: "y", value: yValue - epsilon }]);
rightLimitX = mathjs.evaluate(this.funcText, [{ char: "x", value: xValue }, { char: "y", value: yValue + epsilon }]);
if (Math.abs(rightLimitX - leftLimitX) > 10 * epsilon && Math.abs(rightLimitY - leftLimitY) > 10 * epsilon) {
return false;
}
if ((Math.abs(leftLimitX - zValue!) > 10 * epsilon || Math.abs(rightLimitX - zValue!) > 10 * epsilon) && (Math.abs(leftLimitY - zValue!) > 10 * epsilon || Math.abs(rightLimitY - zValue!) > 10 * epsilon)) {
return false;
}
}
const isLeftInfinite: boolean = Math.abs(leftLimitX) > threshold || Math.abs(leftLimitY) > threshold;
const isRightInfinite: boolean = Math.abs(rightLimitX) > threshold || Math.abs(rightLimitY) > threshold;
if (isLeftInfinite || isRightInfinite) {
return false;
}
} catch {
this.showToast("判断是否为间断点时发生错误,默认此点非连续");
return false;
}
return true;
}
/**
* 一元函数获取点的方法封装
* @param independentVar 自变量
* @param dependentVar 因变量
* @param min 负轴坐标最大值
* @param max 正轴坐标最大值
* @param step 步长值
* @param count 个数
*/
private getPointsBasic(independentVar: string, dependentVar: string, min: number, max: number, step: number, count?: number) {
let _temp: number = Math.abs(max - min) / step;
if (_temp <= 10000) {
for (let argument: number = min; argument <= max && step > 0; argument += step) {
argument = Number(argument.toFixed(4));
let standard: RegExp = new RegExp(`(^|[^a-zA-Z])${independentVar}($|[^a-zA-Z])`)
let calculateRes: number = 0;
if (standard.test(this.funcText)) {
try {
calculateRes = mathjs.evaluate(this.funcText, [{ char: independentVar, value: argument }]);
} catch {
this.coPot.length = 0;
this.showToast("运算过程中出错");
return;
}
}
if (this.judgeContinue(argument, calculateRes)) {
// this.coPot.push({ [independentVar]: argument, [dependentVar]: calculateRes, pointType: PointType.CONTINUE });
this.coPot.push({ pointType: PointType.CONTINUE });
let index: number = this.coPot.length - 1;
if (independentVar == "x") this.coPot[index].x = argument;
else if (independentVar == "y") this.coPot[index].y = argument;
if (dependentVar == "y") this.coPot[index].y = calculateRes;
else if (dependentVar == "z") this.coPot[index].z = calculateRes;
} else {
// this.coPot.push({ [independentVar]: argument, pointType: PointType.DISCONTINUE })
this.coPot.push({ pointType: PointType.DISCONTINUE });
let index: number = this.coPot.length - 1;
if (independentVar == "x") this.coPot[index].x = argument;
else if (independentVar == "y") this.coPot[index].y = argument;
}
if (argument != max && argument + step > max) {
argument = max - step;
}
}
if (count && count > 0 && count < this.coPot.length) {
this.coPot = this.coPot.slice(0, count);
}
} else {
this.coPot.push({ x: 0, y: 0, pointType: PointType.CONTINUE });
this.coPot.length = 1;
this.showToast("请适当增大步长");
}
}
/**
* 获取用于绘制2维图像的各个坐标点
* @returns 返回坐标点信息数组
*/
getPoints2D(): IPoint[] {
this.coPot.length = 0;
if (this.getFuncType(Dimension.TWO) == FuncType.YX) {
this.getPointsBasic("x", "y", this.xMin, this.xMax, this.xStep, this.xCount);
} else if (this.getFuncType(Dimension.TWO) == FuncType.YC) {
this.coPot.push({ y: Number(this.funcText), pointType: PointType.CONTINUE })
} else if (this.getFuncType(Dimension.TWO) == FuncType.XC) {
this.coPot.push({ x: Number(this.funcText), pointType: PointType.CONTINUE })
}
return this.coPot;
}
/**
* 获取用于绘制3维图像的各个坐标点
* @returns 返回坐标点信息数组
*/
getPoints3D(): IPoint[] {
this.coPot.length = 0;
if (this.getFuncType(Dimension.THREE) == FuncType.ZXY) {
let _temp: number = (Math.abs(this.yMax - this.yMin) / this.yStep) * (Math.abs(this.xMax - this.xMin) / this.xStep);
if (_temp <= 10000) {
for (let x: number = this.xMin; x <= this.xMax && this.xStep > 0; x += this.xStep) {
for (let y: number = this.yMin; y <= this.yMax && this.yStep > 0; y += this.yStep) {
x = Number(x.toFixed(4));
y = Number(y.toFixed(4));
let standard: RegExp = new RegExp("(^|[^a-zA-Z])(?:x|y)($|[^a-zA-Z])")
let z: number = 0;
if (standard.test(this.funcText)) {
try {
z = mathjs.evaluate(this.funcText, [{ char: "x", value: x }, { char: "y", value: y }]);
} catch {
this.coPot.length = 0;
this.showToast("运算过程中出错");
return [];
}
}
if (this.judgeContinue(x, y)) {
this.coPot.push({ x: x, y: y, z: z, pointType: PointType.CONTINUE });
} else {
this.coPot.push({ x: x, y: y, pointType: PointType.DISCONTINUE })
}
if (y != this.yMax && y + this.yStep >= this.yMax) {
let temp: number = y + this.yStep + (-this.yMin);
this.col = Number((temp / this.yStep).toFixed(0)) + 1;
y = this.yMax - this.yStep;
}
}
if (x != this.xMax && x + this.xStep >= this.xMax) {
let temp: number = x + this.xStep + (-this.xMin);
this.row = Number((temp / this.xStep).toFixed(0)) + 1;
x = this.xMax - this.xStep;
}
}
} else {
this.coPot.push({ x: 0, y: 0, pointType: PointType.CONTINUE });
this.coPot.length = 1;
this.showToast("请适当增大步长");
}
// let row: number = Math.abs(this.xMax - this.xMin) / this.xStep;
// let col: number = Math.abs(this.yMax - this.yMin) / this.yStep;
// let temp: IPoint[] = [];
// if (this.xCount > 0 && this.yCount > 0 && this.xCount < row && this.yCount < col) {
// for (let i: number = 0; i < this.xCount * col; i += col) {
// temp = temp.concat(this.coPot.slice(i, i + this.yCount));
// }
// this.coPot = temp;
// } else if (this.xCount > 0 && this.xCount < row) {
// this.coPot = this.coPot.slice(0, this.xCount * col);
// } else if (this.yCount > 0 && this.xCount < col) {
// for (let i: number = 0; i < this.coPot.length; i += col) {
// temp = temp.concat(this.coPot.slice(i, i + this.yCount));
// }
// this.coPot = temp;
// }
} else if (this.getFuncType(Dimension.THREE) == FuncType.ZX) {
this.getPointsBasic("x", "z", this.xMin, this.xMax, this.xStep);
} else if (this.getFuncType(Dimension.THREE) == FuncType.ZY) {
this.getPointsBasic("y", "z", this.yMin, this.yMax, this.yStep);
} else if (this.getFuncType(Dimension.THREE) == FuncType.ZC) {
this.coPot.push({ z: Number(this.funcText), pointType: PointType.CONTINUE });
} else if (this.getFuncType(Dimension.THREE) == FuncType.XC) {
this.coPot.push({ x: Number(this.funcText), pointType: PointType.CONTINUE });
} else if (this.getFuncType(Dimension.THREE) == FuncType.YC) {
this.coPot.push({ y: Number(this.funcText), pointType: PointType.CONTINUE });
}
return this.coPot;
}
createNodeTree(func: string) {
this.separateFunc(func);
this.nodeTree = [];
if (this.isValidFunc(func)) {
let temp: RegExp = new RegExp("\\s+", "g");
let machiningFunc: string = this.funcBody;
machiningFunc = machiningFunc.trim().replace(temp, "");
this.nodeTree.push([machiningFunc]);
let leaf: Array<Array<string>> = mathjs.createNode(machiningFunc);
for (let i: number = 0; i < leaf.length; i++) {
if (leaf[i][0]) this.nodeTree.push([]);
else break;
for (let j: number = 0; j < leaf[i].length; j++) {
this.nodeTree[i + 1].push(leaf[i][j]);
}
}
}
return this.nodeTree;
}
}
export let handle: ProcessFunc = new ProcessFunc();

View File

@ -0,0 +1,44 @@
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"metadata": [
{
"name": "ArkTSPartialUpdate",
"value": "true"
}
],
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"orientation": "landscape",
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
]
}
}

View File

@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,5 @@
{
"src": [
"pages/Index"
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "模块描述"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

View File

@ -0,0 +1,35 @@
import hilog from '@ohos.hilog';
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function abilityTest() {
describe('ActsAbilityTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
})
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
})
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
})
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
})
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
})
})
}

View File

@ -0,0 +1,5 @@
import abilityTest from './Ability.test';
export default function testsuite() {
abilityTest();
}

View File

@ -0,0 +1,50 @@
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
import hilog from '@ohos.hilog';
import { Hypium } from '@ohos/hypium';
import testsuite from '../test/List.test';
import window from '@ohos.window';
import Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
export default class TestAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate');
hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? '');
hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? '');
let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator;
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs;
abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments();
hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!');
Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite);
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate');
windowStage.loadContent('testability/pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s',
JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy');
}
onForeground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground');
}
onBackground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground');
}
}

View File

@ -0,0 +1,17 @@
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}

View File

@ -0,0 +1,47 @@
import hilog from '@ohos.hilog';
import TestRunner from '@ohos.application.testRunner';
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
import Want from '@ohos.app.ability.Want';
let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator | undefined = undefined
let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs | undefined = undefined
async function onAbilityCreateCallback() {
hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback');
}
async function addAbilityMonitorCallback(err : Error) {
hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? '');
}
export default class OpenHarmonyTestRunner implements TestRunner {
constructor() {
}
onPrepare() {
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare ');
}
async onRun() {
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run');
abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
const bundleName = abilityDelegatorArguments.bundleName;
const testAbilityName = 'TestAbility';
let lMonitor: AbilityDelegatorRegistry.AbilityMonitor = {
abilityName: testAbilityName,
onAbilityCreate: onAbilityCreateCallback,
};
abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback)
const want: Want = {
bundleName: bundleName,
abilityName: testAbilityName
};
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
abilityDelegator.startAbility(want, (err, data) => {
hilog.info(0x0000, 'testTag', 'startAbility : err : %{public}s', JSON.stringify(err) ?? '');
hilog.info(0x0000, 'testTag', 'startAbility : data : %{public}s',JSON.stringify(data) ?? '');
})
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end');
}
}

View File

@ -0,0 +1,37 @@
{
"module": {
"name": "entry_test",
"type": "feature",
"description": "$string:module_test_desc",
"mainElement": "TestAbility",
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:test_pages",
"abilities": [
{
"name": "TestAbility",
"srcEntry": "./ets/testability/TestAbility.ets",
"description": "$string:TestAbility_desc",
"icon": "$media:icon",
"label": "$string:TestAbility_label",
"exported": true,
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"skills": [
{
"actions": [
"action.system.home"
],
"entities": [
"entity.system.home"
]
}
]
}
]
}
}

View File

@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_test_desc",
"value": "test ability description"
},
{
"name": "TestAbility_desc",
"value": "the test ability"
},
{
"name": "TestAbility_label",
"value": "test label"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Some files were not shown because too many files have changed in this diff Show More