Introdução Funcional
O projeto visa criar uma ferramenta simples para auxiliar no cálculo de quantidade de sementes. Embora a matemática envolvida seja básica e agricultores experientes raramente a utilizem, a demanda surgiu para automatizar o processo. Os parâmetros principais considerados são:
densidade: Densidade de plantio.kwei: Peso de cem sementes em gramas.hectareQuota: Quantidade de sementes por hectare em Kg.furrowSpacing: Espaçamento entre sulcos em cm.seedsPerMeterOfFurrow: Número de sementes por metro de sulco.plantSpacing: Espaçamento entre plantas em cm.decimalPlaces: Número de casas decimais a serem exibidas.
O parâmetro decimalPlaces foi adicionado para controle de precisão durante os testes. As relações de cálculo conhecidas são:
hectareQuota=densidade*kwei/ 10seedsPerMeterOfFurrow= (furrowSpacing/ 100) *densidadeplantSpacing(cm) = 100 / ((furrowSpacing/ 100) *densidade)
Observa-se que a relação entre o espaçamento dos sulcos e a densidade não foi fornecida, impedindo uma interconexão automática entre esses campos no momento.
Configuração do Projeto
Para agilizar o desenvolvimento e evitar a criação de uma interface de usuário complexa, foi utilizado o framwork Element UI do Vue.js. A biblioteca math.js foi empregada para os cálculos. A inicialização do projeto incluiu o Vuex e outras configurações padrão, aproveitando um template pré-existente para otimizar o tempo.
Implementação
Configuração do Roteador (router.js)
O modo de hash foi escolhido para o roteamento, com uma configuração simples de redirecionamento:
const routes = [
{
path: "/seed-calculator",
name: "SeedCalculator",
component: () => import("../views/SeedCalculator.vue"),
},
];
Configuração do math.js
A biblioteca math.js foi ipmortada globalmente no arquivo main.js:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "element-ui/lib/theme-chalk/index.css";
import * as math from "mathjs/number";
Vue.prototype.$math = math;
import {
Form,
FormItem,
Input,
Button,
Col,
Select,
Option,
} from "element-ui";
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Input);
Vue.use(Button);
Vue.use(Col);
Vue.use(Select);
Vue.use(Option);
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
Componente de Página (SeedCalculator.vue)
O componente principal, nomeado SeedCalculator.vue, contém a interface e a lógica de cálculo:
<template>
<div>
<el-col :span="8">
<el-row>
<el-form
:model="seedForm"
ref="seedFormRef"
label-width="150px"
class="seed-form"
>
<el-form-item label="Casas Decimais" prop="decimalPlaces">
<el-select v-model="seedForm.decimalPlaces" placeholder="Selecione">
<el-option
v-for="item in decimalOptions"
:key="item"
:label="item"
:value="item"
>
</el-option>
</el-select>
<span> (Padrão: 2)</span>
</el-form-item>
<el-form-item label="Densidade (sementes/m²)" prop="density">
<el-input v-model.number="seedForm.density" @input="calculateDerivedValues"></el-input>
</el-form-item>
<el-form-item label="Peso de 100 sementes (g)" prop="kwei">
<el-input v-model.number="seedForm.kwei" @input="calculateDerivedValues"></el-input>
</el-form-item>
<el-form-item label="Quota por Hectare (Kg)" prop="hectareQuota">
<el-input
v-model="seedForm.hectareQuota"
disabled
placeholder="Densidade * Peso 100 sementes / 10"
></el-input>
</el-form-item>
<el-form-item label="Espaçamento Sulcos (cm)" prop="furrowSpacing">
<el-input
v-model.number="seedForm.furrowSpacing"
@input="calculateDerivedValues"
placeholder="Ex: 65"
></el-input>
<span> (Relação com densidade não definida)</span>
</el-form-item>
<el-form-item label="Sementes/Metro de Sulco" prop="seedsPerMeterOfFurrow">
<el-input
v-model="seedForm.seedsPerMeterOfFurrow"
disabled
placeholder="(Espaçamento Sulcos / 100) * Densidade"
></el-input>
</el-form-item>
<el-form-item label="Espaçamento Plantas (cm)" prop="plantSpacing">
<el-input
v-model="seedForm.plantSpacing"
disabled
placeholder="100 / Sementes/Metro de Sulco"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">Calcular</el-button>
<el-button @click="resetForm">Limpar</el-button>
</el-form-item>
</el-form>
</el-row>
</el-col>
</div>
</template>
<script>
import { create, all } from "mathjs/number";
export default {
data() {
const validatePositiveNumber = (rule, value, callback) => {
if (value === null || value === "") {
callback(new Error(rule.message));
} else if (typeof value === 'number' && value > 0 && isFinite(value)) {
callback();
} else {
callback(new Error("Deve ser um número positivo válido."));
}
};
return {
seedForm: {
density: null,
kwei: null,
hectareQuota: "",
furrowSpacing: 65,
seedsPerMeterOfFurrow: "",
plantSpacing: null,
decimalPlaces: 2
},
rules: {
density: [
{ required: true, message: "A densidade é obrigatória", trigger: "blur" },
{ validator: validatePositiveNumber, message: "A densidade deve ser um número positivo.", trigger: "blur" }
],
kwei: [
{ required: true, message: "O peso de 100 sementes é obrigatório", trigger: "blur" },
{ validator: validatePositiveNumber, message: "O peso de 100 sementes deve ser um número positivo.", trigger: "blur" }
],
furrowSpacing: [
{ required: true, message: "O espaçamento entre sulcos é obrigatório", trigger: "blur" },
{ validator: validatePositiveNumber, message: "O espaçamento entre sulcos deve ser um número positivo.", trigger: "blur" }
]
},
decimalOptions: [0, 1, 2, 3, 4],
mathInstance: create(all)
};
},
methods: {
roundToDecimalPlaces(value, decimals) {
if (value === null || isNaN(value)) return "";
const factor = Math.pow(10, decimals);
return Math.round(value * factor) / factor;
},
calculateDerivedValues() {
if (this.seedForm.density === null || this.seedForm.kwei === null || this.seedForm.furrowSpacing === null) {
this.seedForm.hectareQuota = "";
this.seedForm.seedsPerMeterOfFurrow = "";
this.seedForm.plantSpacing = null;
return;
}
const { density, kwei, furrowSpacing, decimalPlaces } = this.seedForm;
// Calcular Quota por Hectare
const calculatedHectareQuota = this.mathInstance.chain(density).multiply(kwei).divide(10).done();
this.seedForm.hectareQuota = this.roundToDecimalPlaces(calculatedHectareQuota, decimalPlaces);
// Calcular Sementes por Metro de Sulco
const calculatedSeedsPerMeter = this.mathInstance.chain(furrowSpacing).divide(100).multiply(density).done();
this.seedForm.seedsPerMeterOfFurrow = this.roundToDecimalPlaces(calculatedSeedsPerMeter, decimalPlaces);
// Calcular Espaçamento entre Plantas
if (calculatedSeedsPerMeter > 0) {
const calculatedPlantSpacing = this.mathInstance.chain(100).divide(calculatedSeedsPerMeter).done();
this.seedForm.plantSpacing = this.roundToDecimalPlaces(calculatedPlantSpacing, decimalPlaces);
} else {
this.seedForm.plantSpacing = null;
}
},
submitForm() {
this.$refs.seedFormRef.validate((valid) => {
if (valid) {
this.calculateDerivedValues();
this.$message({
message: "Cálculos realizados com sucesso!",
type: "success"
});
} else {
this.$message.error("Por favor, corrija os erros no formulário.");
return false;
}
});
},
resetForm() {
this.$refs.seedFormRef.resetFields();
this.seedForm.hectareQuota = "";
this.seedForm.seedsPerMeterOfFurrow = "";
}
},
watch: {
'seedForm.decimalPlaces'(newVal) {
// Recalcula os valores quando as casas decimais mudam
this.calculateDerivedValues();
}
},
mounted() {
// Realiza o cálculo inicial se houver valores padrão preenchidos
if (this.seedForm.density && this.seedForm.kwei && this.seedForm.furrowSpacing) {
this.calculateDerivedValues();
}
}
};
</script>
<style scoped>
.seed-form {
padding: 20px;
border: 1px solid #eee;
border-radius: 5px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
}
.el-form-item span {
font-size: 0.8em;
color: #909399;
margin-left: 10px;
}
</style>
Dicas Úteis
Validação Personalizada
A validação de formulário pode ser estendida com regras personalizadas. No exemplo abaixo, uma função validatePositiveNumber é definida para garantir que os campos numéricos sejam positivos e válidos:
const validatePositiveNumber = (rule, value, callback) => {
if (value === null || value === "") {
callback(new Error(rule.message));
} else if (typeof value === 'number' && value > 0 && isFinite(value)) {
callback();
} else {
callback(new Error("Deve ser um número positivo válido."));
}
};
Esta função é então asssociada a um campo de regras específico:
rules: {
density: [
{ required: true, message: "A densidade é obrigatória", trigger: "blur" },
{ validator: validatePositiveNumber, message: "A densidade deve ser um número positivo.", trigger: "blur" }
],
// ... outras regras
}
Utilização do math.js
O math.js oferece diversas funções matemáticas. Para operações encadeadas, utilize o método chain():
// Exemplo: Calcular (9000 / 100) * 2
const result = this.mathInstance.chain(9000)
.divide(100)
.multiply(2)
.done();
Para formatação de números com precisão especificada:
const preciseResult = this.mathInstance.format(result, { precision: 14 });
// Ou com um número inteiro de casas decimais
const formattedResult = this.mathInstance.format(result, 4);
Na implementação deste projeto, foi utilizada uma função auxiliar roundToDecimalPlaces para arredondamento simples, que é mais adequada para este caso de uso específico do que o math.format.
Controle de Precisão Decimal
Para garantir a precisão desejada nos cálculos, a função roundToDecimalPlaces foi implementada. Diferente do método toFixed() que retorna uma string e pode ter comportamentos inesperados com números de ponto flutuante, Math.round() combinado com fatores de potência de 10 oferece um controle numérico mais confiável para arredondamento:
/**
* Arredonda um valor para um número específico de casas decimais.
* @param {number} value - O valor numérico a ser arredondado.
* @param {number} decimals - O número de casas decimais a manter.
* @returns {number|string} O valor arredondado ou string vazia se a entrada for inválida.
*/
roundToDecimalPlaces(value, decimals) {
if (value === null || isNaN(value)) return "";
const factor = Math.pow(10, decimals);
return Math.round(value * factor) / factor;
},