HARMONYOS应用实例267:树状图求概率
·
- 树状图求概率
- 功能:动画生成两次或三次事件的树状图,自动计算所有可能结果数和符合条件的结果数。
支持生成2次或3次事件的树状图
动画展示树状图的生成过程
自动计算所有可能结果数
自动计算符合条件的结果数
自动计算概率值
支持自定义事件结果(如A、B、C等)
支持自定义条件(如"AA"、"AB"等)
显示所有可能结果和符合条件的结果
- 功能:动画生成两次或三次事件的树状图,自动计算所有可能结果数和符合条件的结果数。
// 树状图求概率
// 功能:动画生成两次或三次事件的树状图,自动计算所有可能结果数和符合条件的结果数
// 事件配置接口
interface EventConfig {
eventCount: 2 | 3;
events: string[];
probabilities: number[];
condition: string;
}
// 结果节点接口
interface ResultNode {
id: string;
value: string;
probability: number;
level: number;
parentId: string | null;
path: string;
}
@Entry
@Component
struct TreeDiagramProbability {
@State canvasWidth: number = 350;
@State canvasHeight: number = 350;
@State eventConfig: EventConfig = {
eventCount: 2,
events: ['A', 'B'],
probabilities: [0.5, 0.5],
condition: 'A'
};
@State resultNodes: ResultNode[] = [];
@State allResults: string[] = [];
@State conditionResults: string[] = [];
@State showAnimation: boolean = false;
@State animationProgress: number = 0;
@State selectedEvents: string[] = ['A', 'B'];
@State inputEvent: string = '';
@State isCalculated: boolean = false;
build() {
Column({ space: 15 }) {
Text('树状图求概率')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
Column() {
Text('功能介绍')
.fontSize(18)
.fontWeight(FontWeight.Medium)
Text('动画生成两次或三次事件的树状图,自动计算所有可能结果数和符合条件的结果数,帮助理解概率计算')
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Center)
}
.width('100%')
.backgroundColor('#E3F2FD')
.borderRadius(10)
.padding(15)
Column({ space: 10 }) {
Text('事件配置')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
Column({ space: 8 }) {
Text('事件次数')
.fontSize(14)
.fontColor('#666666')
Row({ space: 10 }) {
Button('2次')
.width('48%')
.height(40)
.fontSize(14)
.backgroundColor(this.eventConfig.eventCount === 2 ? '#2196F3' : '#E0E0E0')
.onClick(() => {
this.eventConfig.eventCount = 2
})
Button('3次')
.width('48%')
.height(40)
.fontSize(14)
.backgroundColor(this.eventConfig.eventCount === 3 ? '#2196F3' : '#E0E0E0')
.onClick(() => {
this.eventConfig.eventCount = 3
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.width('100%')
.padding(8)
.backgroundColor('#F5F5F5')
.borderRadius(6)
Column({ space: 8 }) {
Text('事件结果')
.fontSize(14)
.fontColor('#666666')
Row({ space: 10 }) {
TextInput({ placeholder: '输入事件' })
.width('60%')
.height(40)
.fontSize(14)
.onChange((value: string) => {
this.inputEvent = value
})
Button('添加')
.width('35%')
.height(40)
.fontSize(14)
.backgroundColor('#4CAF50')
.onClick(() => {
this.addEvent()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
if (this.selectedEvents.length > 0) {
Flex({
direction: FlexDirection.Row,
wrap: FlexWrap.Wrap,
justifyContent: FlexAlign.Start
}) {
ForEach(this.selectedEvents, (event: string, index: number) => {
Row({ space: 5 }) {
Text(event)
.fontSize(14)
.fontColor('#2196F3')
Button('×')
.width(20)
.height(20)
.fontSize(12)
.backgroundColor('#F44336')
.onClick(() => {
this.removeEvent(index)
})
}
.padding(5)
.backgroundColor('#E3F2FD')
.borderRadius(4)
})
}
.width('100%')
}
}
.width('100%')
.padding(8)
.backgroundColor('#F5F5F5')
.borderRadius(6)
Column({ space: 8 }) {
Text('条件')
.fontSize(14)
.fontColor('#666666')
TextInput({ placeholder: '输入条件,如 "AA"' })
.width('100%')
.height(40)
.fontSize(14)
.onChange((value: string) => {
this.eventConfig.condition = value
})
}
.width('100%')
.padding(8)
.backgroundColor('#F5F5F5')
.borderRadius(6)
Row({ space: 10 }) {
Button('生成树状图')
.width('48%')
.height(50)
.fontSize(16)
.backgroundColor('#4CAF50')
.onClick(() => {
this.generateTree()
})
Button('重置')
.width('48%')
.height(50)
.fontSize(16)
.backgroundColor('#FF9800')
.onClick(() => {
this.reset()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.width('90%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(10)
Column({ space: 10 }) {
Text('树状图')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
Canvas(this.canvasContext)
.width(this.canvasWidth)
.height(this.canvasHeight)
.backgroundColor('#FFFFFF')
.border({ width: 2, color: '#333' })
.borderRadius(10)
.onReady(() => {
this.drawTree()
})
}
.width('100%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(10)
Column({ space: 10 }) {
Text('计算结果')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
if (this.isCalculated) {
Column({ space: 8 }) {
Text(`所有可能结果数: ${this.allResults.length}`)
.fontSize(16)
.fontColor('#2196F3')
.fontWeight(FontWeight.Bold)
Text(`符合条件的结果数: ${this.conditionResults.length}`)
.fontSize(16)
.fontColor('#4CAF50')
.fontWeight(FontWeight.Bold)
Text(`概率: ${this.calculateProbability().toFixed(4)}`)
.fontSize(18)
.fontColor('#FF9800')
.fontWeight(FontWeight.Bold)
Text('所有可能结果:')
.fontSize(14)
.fontColor('#666666')
Text(this.allResults.join(', '))
.fontSize(14)
.fontColor('#333333')
.textAlign(TextAlign.Center)
Text('符合条件的结果:')
.fontSize(14)
.fontColor('#666666')
Text(this.conditionResults.join(', '))
.fontSize(14)
.fontColor('#4CAF50')
.textAlign(TextAlign.Center)
}
.width('100%')
.padding(15)
.backgroundColor('#E8F5E9')
.borderRadius(10)
} else {
Column() {
Text('请先生成树状图')
.fontSize(14)
.fontColor('#999999')
}
.width('100%')
.padding(15)
.backgroundColor('#F5F5F5')
.borderRadius(10)
}
}
.width('100%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(10)
Column({ space: 8 }) {
Text('使用说明')
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text('• 选择事件次数(2次或3次)')
.fontSize(14)
.fontColor('#666666')
Text('• 输入事件结果(如 A, B)')
.fontSize(14)
.fontColor('#666666')
Text('• 输入条件(如 AA)')
.fontSize(14)
.fontColor('#666666')
Text('• 点击"生成树状图"')
.fontSize(14)
.fontColor('#666666')
Text('• 观察树状图和计算结果')
.fontSize(14)
.fontColor('#666666')
Text('• 理解概率的计算方法')
.fontSize(14)
.fontColor('#666666')
}
.width('95%')
.padding(12)
.backgroundColor('#FFF3E0')
.borderRadius(10)
}
.width('100%')
.height('100%')
.padding(10)
.justifyContent(FlexAlign.Start)
}
private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D();
private addEvent() {
if (this.inputEvent.trim() !== '' && this.selectedEvents.length < 4) {
this.selectedEvents.push(this.inputEvent.trim())
this.inputEvent = ''
}
}
private removeEvent(index: number) {
this.selectedEvents.splice(index, 1)
}
private generateTree() {
if (this.selectedEvents.length < 2) {
return
}
this.resultNodes = []
this.allResults = []
this.conditionResults = []
const eventCount = this.eventConfig.eventCount
const events = this.selectedEvents
const probability = 1 / events.length
this.generateNodes(events, eventCount, probability)
this.calculateResults()
this.isCalculated = true
this.showAnimation = true
this.animationProgress = 0
const totalSteps = 100
let step = 0
const animate = () => {
if (step <= totalSteps) {
this.animationProgress = step / totalSteps
this.drawTree()
step++
setTimeout(animate, 30)
} else {
this.showAnimation = false
}
}
animate()
}
private generateNodes(events: string[], eventCount: number, probability: number) {
const generateLevel = (level: number, parentId: string | null, path: string) => {
if (level > eventCount) {
return
}
events.forEach((event: string, index: number) => {
const id = `${parentId ? parentId + '-' : ''}${event}${index}`
const newPath = path + event
const node: ResultNode = {
id,
value: event,
probability,
level,
parentId,
path: newPath
}
this.resultNodes.push(node)
generateLevel(level + 1, id, newPath)
})
}
generateLevel(1, null, '')
}
private calculateResults() {
const leafNodes = this.resultNodes.filter((node: ResultNode) => {
return node.level === this.eventConfig.eventCount
})
leafNodes.forEach((node: ResultNode) => {
this.allResults.push(node.path)
if (node.path === this.eventConfig.condition) {
this.conditionResults.push(node.path)
}
})
}
private calculateProbability(): number {
if (this.allResults.length === 0) {
return 0
}
return this.conditionResults.length / this.allResults.length
}
private drawTree() {
const ctx = this.canvasContext
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
if (this.resultNodes.length === 0) {
ctx.fillStyle = '#999999'
ctx.font = '16px sans-serif'
ctx.fillText('请先生成树状图', this.canvasWidth / 2 - 80, this.canvasHeight / 2)
return
}
const padding = 40
const treeWidth = this.canvasWidth - 2 * padding
const treeHeight = this.canvasHeight - 2 * padding
const levelHeight = treeHeight / (this.eventConfig.eventCount + 1)
const levelNodes = new Map<number, ResultNode[]>()
this.resultNodes.forEach((node: ResultNode) => {
let nodes = levelNodes.get(node.level)
if (!nodes) {
nodes = []
levelNodes.set(node.level, nodes)
}
nodes.push(node)
})
levelNodes.forEach((nodes: ResultNode[], level: number) => {
const levelWidth = treeWidth / (nodes.length + 1)
nodes.forEach((node: ResultNode, index: number) => {
const x = padding + (index + 1) * levelWidth
const y = padding + level * levelHeight
if (this.showAnimation) {
const progress = Math.min(1, this.animationProgress * 2 - (level - 1) * 0.3)
if (progress > 0) {
ctx.globalAlpha = progress
this.drawNode(ctx, x, y, node)
this.drawEdges(ctx, node, levelNodes, levelWidth, levelHeight, padding, progress)
ctx.globalAlpha = 1
}
} else {
this.drawNode(ctx, x, y, node)
this.drawEdges(ctx, node, levelNodes, levelWidth, levelHeight, padding, 1)
}
})
})
}
private drawNode(ctx: CanvasRenderingContext2D, x: number, y: number, node: ResultNode) {
ctx.fillStyle = '#2196F3'
ctx.beginPath()
ctx.arc(x, y, 15, 0, 2 * Math.PI)
ctx.fill()
ctx.strokeStyle = '#333'
ctx.lineWidth = 2
ctx.stroke()
ctx.fillStyle = '#FFFFFF'
ctx.font = '12px sans-serif'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(node.value, x, y)
if (node.level === this.eventConfig.eventCount) {
ctx.fillStyle = '#333'
ctx.font = '10px sans-serif'
ctx.fillText(node.path, x, y + 25)
}
}
private drawEdges(ctx: CanvasRenderingContext2D, node: ResultNode, levelNodes: Map<number, ResultNode[]>, levelWidth: number, levelHeight: number, padding: number, progress: number) {
if (node.parentId) {
const parentLevel = node.level - 1
const parentNodes = levelNodes.get(parentLevel)
if (parentNodes) {
parentNodes.forEach((parentNode: ResultNode) => {
if (parentNode.id === node.parentId) {
const parentX = padding + (parentNodes.indexOf(parentNode) + 1) * levelWidth
const parentY = padding + parentLevel * levelHeight
const currentNodes = levelNodes.get(node.level)
if (currentNodes) {
const currentX = padding + (currentNodes.indexOf(node) + 1) * levelWidth
const currentY = padding + node.level * levelHeight
ctx.strokeStyle = '#333'
ctx.lineWidth = 2
ctx.beginPath()
ctx.moveTo(parentX, parentY + 15)
ctx.lineTo(currentX, parentY + 15 + (currentY - parentY - 30) * progress)
ctx.stroke()
}
}
})
}
}
}
private reset() {
this.resultNodes = []
this.allResults = []
this.conditionResults = []
this.selectedEvents = ['A', 'B']
this.inputEvent = ''
this.eventConfig = {
eventCount: 2,
events: ['A', 'B'],
probabilities: [0.5, 0.5],
condition: 'A'
}
this.isCalculated = false
this.showAnimation = false
this.animationProgress = 0
this.drawTree()
}
}
更多推荐


所有评论(0)