Ir ao conteúdo
  • Cadastre-se

C Programa de console em C para visualizar gráficos/formas de onda


Posts recomendados

Boa noite, colegas! :)

 

Introdução:

Recentemente tive a ideia de criar um programinha de console para plotar gráficos de Y em função de X com ajuda dos cabeçalhos math.h e windows.h, bastando inserir a equação como um dos argumentos de uma função (macro), obtendo assim as coordenadas que podem ser passadas para outra função responsável por plotar o gráfico.

 

Código:

#define redColor RGB(255,0,0)
#define blueColor RGB(0,0,255)
#define greenColor RGB(0,255,0)
#define whiteColor RGB(255,255,255)

#define getCoords(x, y, indxOp, xStartVal, xIncrm, pntsQtd, sizeXY, alocXY)     \
do {                                                                            \
    int alocAux = alocXY;                                                       \
    int xValAux = xStartVal;                                                    \
    for(int i = 0; i < pntsQtd; i++, xValAux+=xIncrm, sizeXY++){                \
        if(i >= (alocAux/sizeof(int))){                                         \
            alocAux += alocXY;                                                  \
            x = (int*)realloc(x, alocAux);                                      \
            y = (int*)realloc(y, alocAux);                                      \
        }                                                                       \
        x[i] = xValAux;                                                         \
        y[i] = indxOp;                                                          \
    }                                                                           \
} while (0)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <windows.h>
  
/* Programa escrito por Lucca Rodrigues - 10/04/2021
Fonte: www.clubedohardware.com.br/forum/177-ccc
Acesso em: 14/04/2021 */

void drawGraph(int *x, int *y, int sizeXY, int widthLine, int lineStyle, int originX,
               int originY, int lenAbscisRL, int lenOrdinRL, COLORREF lineColor, COLORREF axisColor){
    HWND console = GetConsoleWindow();
	HDC hdc = GetDC(console);

	for(int i = 0; i < sizeXY; i++){
        if(x[i] >= 0 && y[i] >= 0){
            // Primeiro quadrante
            if((x[i] > lenAbscisRL) || (y[i] > lenOrdinRL)){
                sizeXY--;
                for(int j = i; j < sizeXY; j++){
                    x[j] = x[j+1];
                    y[j] = y[j+1];
                }
                i--;
            } else{
                x[i] += originX;
                y[i] = originY - y[i];
            }
        } else if(x[i] <= 0 && y[i] >= 0){
            // Segundo quadrante
            if((x[i] < -lenAbscisRL) || (y[i] > lenOrdinRL)){
                sizeXY--;
                for(int j = i; j < sizeXY; j++){
                    x[j] = x[j+1];
                    y[j] = y[j+1];
                }
                i--;
            } else{
                x[i] = originX + x[i];
                y[i] = originY - y[i];
            }
        } else if(x[i] <= 0 && y[i] <= 0){
            // Terceiro quadrante
            if((x[i] < -lenAbscisRL) || (y[i] < -lenOrdinRL)){
                sizeXY--;
                for(int j = i; j < sizeXY; j++){
                    x[j] = x[j+1];
                    y[j] = y[j+1];
                }
                i--;
            } else{
                x[i] = originX + x[i];
                y[i] = originY - y[i];
            }
        } else if(x[i] >= 0 && y[i] <= 0){
            // Quarto quadrante
            if((x[i] > lenAbscisRL) || (y[i] < -lenOrdinRL)){
                sizeXY--;
                for(int j = i; j < sizeXY; j++){
                    x[j] = x[j+1];
                    y[j] = y[j+1];
                }
                i--;
            } else{
                x[i] += originX;
                y[i] = originY - y[i];
            }
        }


	}

	SelectObject(hdc, CreatePen(lineStyle, widthLine, lineColor));

    for(int i = 0; i < (sizeXY - 1); i++){
        MoveToEx(hdc, x[i], y[i], NULL);
        LineTo(hdc, x[i+1], y[i+1]);
    }

    SelectObject(hdc, CreatePen(PS_SOLID, 1, axisColor));

    MoveToEx(hdc, originX, originY-lenOrdinRL, NULL);
    LineTo(hdc, originX, originY+lenOrdinRL);

    MoveToEx(hdc, originX-lenAbscisRL, originY, NULL);
    LineTo(hdc, originX+lenAbscisRL, originY);

    ReleaseDC(console, hdc);

    return;
}

int main()
{
    int *x = (int*)malloc(10*sizeof(int));
    int *y = (int*)malloc(10*sizeof(int));
    int sizeXY = 0;

    //getCoords(x, y, indxOp, xStartVal, xIncrm, pntsQtd, sizeXY, alocXY)
    getCoords(x, y, 50.0f*sin((double)x[i]/5.0f), -200, 1, 400, sizeXY, 10*sizeof(int));

    //void drawGraph(int *x, int *y, int sizeXY, int widthLine, int lineStyle, int originX,
    //           int originY, int lenAbscisRL, int lenOrdinRL, COLORREF lineColor, COLORREF axisColor)
    drawGraph(x, y, sizeXY, 2, PS_SOLID, 100, 100, 100, 100, redColor, whiteColor);

    free(x);
    free(y);
    x = NULL;
    y = NULL;

    printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");

    return 0;
}

 

Para compilar no CodeBlocks é necessário fazer o seguinte: na área de management, clicar com o botão direito no projeto > Build options > Linker settings > Other linker options e digitar -lgdi32 > Ok.

Não testei o código no Visual Studio ainda.

 

Uma breve explicação:

Para desenhar o gráfico, estou usando as funções MoveToEx() e LineTo(), fora isso, só preciso inserir cada coordenada como num sistema de coordenadas cartesiano, que é o que parte da função drawGraph() faz. A outra parte é responsável por desenhar as linhas que compõem a forma de onda.

A macro getCoords é uma macro do tipo função, e um dos parâmetros dessa função é a equação que será atribuída a Y, e caso haja X nessa equação, o mesmo deverá ser passado indexado com um índice i (conforme o exemplo no código), já que as macros são substituídas por suas definições durante o tempo de compilação, e cada coordenada é referenciada por um índice.

Os parâmetros das funções eu tentei deixar autoexplicativos.

drawGraph():

  • int *x e int *y - ponteiros que apontarão para o mesmo local da memória que os ponteiros passados para a função;
  • int sizeXY - a quantidade de coordenadas de X e Y;
  • int widthLine - a espessura da linha da forma de onda;
  • int lineStyle - o estilo da linha (vide documentação a respeito de CreatePen());
  • int originX - a Origem do eixo X;
  • int originY - a Origem do eixo Y;
  • int lenAbscisRL - o comprimento da abscissa em ambas direções;
  • int lenOrdinRL - o comprimento da ordenada em ambas direções;
  • COLORREF lineColor - a coloração da linha da forma de onda;
  • COLORREF axisColor - a coloração das linhas que compõem o plano cartesiano.

getCoords()

  • x e y - ponteiros para armazenar as coordenadas;
  • indxOp - a equação, e se X estiver nela, o mesmo deverá estar indexado com um índice i;
  • xStartVal - um valor inicial que será atribuído à X;
  • xIncrm - o incremento que parte do valor inicial até atingir a quantidade total de pontos;
  • pntsQtd - quantidade total de pontos;
  • sizeXY - a quantidade de coordenadas de X e Y;
  • alocXY - o tamanho do bloco de memória que deve ser somado ao tamanho do bloco de memória apontado por x e y (se necessário).

 

Resultados:

Plotagem de uma senoide comum - função matemática:

image.png.0443cf327b43fd87b2cfd5803cc18957.png

Plotagem de uma onda quadrada - função matemática:

image.png.27bc165c733f1426d3743d268fe4701f.png

Plotagem de uma onda triangular - função matemática:

image.png.6c40e85bb1120c8db6a96b84d73603f8.png

Plotagem de um coração - 2 funções condicionadas:

image.png.c1db2f8023dc7c1b571ba7cc1c1a660d.png

Plotagem de... Algo bem estranho - função matemática:

image.png.9e5428fb4a1206ef4445d659e8541152.png

 

Algumas mudanças que pretendo fazer:

  • Pretendo futuramente acrescentar um parâmetro na função drawGraph() que fará com que a escala do gráfico mude, já que gráficos com coordenadas pequenas são dificilmente visualizados;
  • Acredito que o parâmetro alocXY da função getCoords() não seja necessário, pretendo escolher um valor para o mesmo e não deixar que o usuário passe esse valor para a função;
  • Acrescentarei uma struct para servir de formulário e facilitar o processo;
  • Mudarei o tipo da função drawGraph(), pois o mesmo é void e isso só atrapalha quando a questão é identificar problemas;
  • Quando uso o scroll do mouse, o gráfico é apagado. Não sei a razão disso, pretendo corrigir futuramente se possível.

 

Por fim...

Se falhei em termos técnicos, peço para que me corrijam, pois ainda estou aprendendo :D

Se quiserem acrescentar algo ou dar um feedback, fiquem à vontade!

  • Curtir 4
  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

@Lucca Rodrigues     muito bom , esse código ,  se você puder poste as fórmulas para gerar os outras formas de ondas ,  e testei aqui no visual studio e funciona bem , e nem precisa de colocar aquele Link   "  -lgdi32 do compilador ,   e quais instruções desse código , conseguem fixar o desenho na console  ?  pois os outro que fiz , ao mover a console o desenho se desfaz . 

o print da tela no VS :

graficos_1.thumb.jpg.378a35366c0a3d1a863eb55b546e586c.jpg

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

@devair1010

5 horas atrás, devair1010 disse:

se você puder poste as fórmulas para gerar os outras formas de ondas

Eu testei algumas, são essas:

x[i]
-x[i]
abs(x[i])
((pow((double)x[i],2.0f))-(20.0f*(double)x[i])-50.0f)/10.0f
50.0f*sin((double)x[i]/5.0f)
sqrt(20.0f*(double)x[i])
exp((double)x[i]/5.0f)
(log10((double)x[i]*50.0f))*5.0f
(sin((double)x[i])/23.0f*(pow((double)x[i], 2.0f)))
fabs(50.0f*sin((double)x[i]/5.0f))
20.0f*exp(cos((double)x[i]/5.0f)) - 20.0f*exp(sin((double)x[i]/5.0f))
(100.0f/M_PI)*asin(sin((M_PI*(double)x[i])/25.0f))
(100.0f/(pow(100.0f, (100.0f*sin((-0.1f)*(double)x[i])))+1.0f))-(100.0f/2.0f)

As operações são feitas com pontos flutuantes, e depois há uma conversão implícita para inteiro ao se atribuir o resultado à Y. Isso é necessário, pois se as operações forem feitas com inteiros, as ondas ficam todas meio quadradas:

image.png.93847fb27870b7b44f66fc50cdf9bfc6.png

 

5 horas atrás, devair1010 disse:

testei aqui no visual studio e funciona bem , e nem precisa de colocar aquele Link   "  -lgdi32 do compilador

Que bom!

 

5 horas atrás, devair1010 disse:

quais instruções desse código , conseguem fixar o desenho na console  ?  pois os outro que fiz , ao mover a console o desenho se desfaz

Não sei o motivo por enquanto. Isso é uma das coisas que eu havia dito que pretendo corrigir:

Em 14/04/2021 às 22:10, Lucca Rodrigues disse:

Quando uso o scroll do mouse, o gráfico é apagado. Não sei a razão disso, pretendo corrigir futuramente se possível.

Alguém com mais experiência talvez saberia dar uma luz... Talvez @arfneto

Eu dei uma pesquisada mas não encontrei nada a respeito.

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

Não sei se entendo isso :(

 

Então sizeXY é a quantidade de coordenadas e pntsQtd é a quantidade total de pontos? Qual a diferença?

 

os vetores X e Y são pntsQtd pares da forma y = f(x) e é isso que vai para o gráfico?

 

Porque "pode precisar"  realocar x[] e y[]?

 

O que acontece se x não estiver na equação?

 

Porque usar um #define? Qual a vantagem?

 

Em 14/04/2021 às 22:10, Lucca Rodrigues disse:

macros são substituídas por suas definições durante o tempo de compilação

 

De certo modo. macros são substituídas antes da compilação.

  • Curtir 1
  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

@arfneto

2 horas atrás, arfneto disse:

Então sizeXY é a quantidade de coordenadas e pntsQtd é a quantidade total de pontos? Qual a diferença?

Bom ponto. Não há diferença, pretendo retirar pntsQtd futuramente...

 

2 horas atrás, arfneto disse:

os vetores X e Y são pntsQtd pares da forma y = f(x) e é isso que vai para o gráfico?

Sim.

 

2 horas atrás, arfneto disse:

Porque "pode precisar"  realocar x[] e y[]?

Pra ter memória suficiente para armazenar todas as coordenadas, e a quantidade é representada por pntsQtd.

 

2 horas atrás, arfneto disse:

O que acontece se x não estiver na equação?

Daí eu imagino que Y seja constante. Me recordo de ter testado isso uma vez somente...

 

2 horas atrás, arfneto disse:

Porque usar um #define? Qual a vantagem?

Porque eu preciso que Y receba algo que eu não consigo passar como argumento em uma função comum. Eu preciso de uma macro do tipo função.

 

2 horas atrás, arfneto disse:

Não sei se entendo isso :(

Nem eu :D

As funções que desenham o gráfico são:

Em 14/04/2021 às 22:10, Lucca Rodrigues disse:

as funções MoveToEx() e LineTo()

E o problema seria:

Em 14/04/2021 às 22:10, Lucca Rodrigues disse:

Quando uso o scroll do mouse, o gráfico é apagado. Não sei a razão disso, pretendo corrigir futuramente se possível.

Mas não sei o motivo realmente... Pesquisei e não encontrei nada a respeito.

Talvez um programa para desktop seja melhor.

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

24 minutos atrás, Lucca Rodrigues disse:

Pra ter memória suficiente para armazenar todas as coordenadas, e a quantidade é representada por pntsQtd

 

Sim, mas você deu uma volta grande pra fazer isso.

 

25 minutos atrás, Lucca Rodrigues disse:

Daí eu imagino que Y seja constante. Me recordo de ter testado isso uma vez somente..

 

:D sim. se y = f(x) não depende de x então é constante até onde o eixo y pode saber

 

26 minutos atrás, Lucca Rodrigues disse:

Porque eu preciso que Y receba algo que eu não consigo passar como argumento em uma função comum. Eu preciso de uma macro do tipo função

 

Não, Precisa apenas de algo do tipo FX onde FX é 

 

    typedef     int(FX)(int);

 

ou algo do tipo int()(int), que é como o compilador vê isso.

 

28 minutos atrás, Lucca Rodrigues disse:

Nem eu :D

As funções que desenham o gráfico são

 

Eu falava era da lógica que eu tentava entender... O lance do desenho é porque a primeira vez que precisar redesenhar a console o sistema vai perder a configuração que possibilitou o desenho, já que o programa terminou com um ReleaseDC().

 

Rodando no IDE eu vejo o desenho. Mas no terminal ou na console nada aparece. Veja:

 

image.thumb.png.0600394938215899bd830d410fd96de3.png

 

Porque ao encerrar o programa o contexto é destruído.

 

Colocando uma pausa no programa ainda se vê o desenho na console, mas no Terminal também não

 

image.thumb.png.16bdcfcbdb5d36d11ab07a9a34067901.png

 

 

Meu palpite: não use nunca essas coisas em um programa gerado para o subsistema console. Apenas Windows. 

 

De volta ao seu programa

 

Eu não entendi a lógica do plot ao certo, mas confesso que não li com atenção e deixei drawGraph() pra ver depois.

 

Usei um pouco de recortar e colar em seu código e vou postar para você comparar com o modo como escreveu.

 

Acho que sabe que eu iria começar com aquela história de "escrever em torno dos dados" e escrever de modo iterativo e nunca interativo até estar confortável com a lógica e tal. Então nem vou escrever. Mas eu iria :D 

 

Seus dados estão em 3 partes até onde eu entendi:

  • a definição da tela
  • o conjunto de pontos de dados
  • a configuração da amostra

Uma definição para a tela podia ter sido
 

typedef     struct
{
    int     lenAbscisRL;
    int     lenOrdinRL;
    int     lineStyle;
    int     originX;
    int     originY;
    int     widthLine;
    COLORREF    lineColor;
    COLORREF    axisColor;

}   Screen;

 

E assim podia usar a mesma função com várias telas pra testar. Sem mexer no programa ou nos ONZE argumentos de drawGraph() :D 


E claro pode usar constantes nos testes, como essa que descreve seu exemplo:
 

    Screen tela1 =
    {
        .widthLine = 2,
        .lineStyle = PS_SOLID,
        .originX = 100,
        .originY = 100,
        .lenAbscisRL = 100,
        .lenOrdinRL = 100,
        .lineColor = redColor,
        .axisColor = whiteColor
    };

 

E os dados? Veja essa:

 

typedef     struct
{
    unsigned    points;
    int*        x;
    int*        y;
    int         start;
    int         increment;
    FX*         funcao;

}   Data;

 

Usando encapsulamento, até a função pode (e deve) ficar dentro de Data, porque assim pode usar várias funções na mesma tela sem mexer no programa... O que é FX? FX é int()(int), como eu te disse. 

 

Claro que o * poderia estar no typedef mas eu sempre vou recomendar: JAMAIS use um typedef para um ponteiro a menos que o cara que vai te dar a nota ou assinar o cheque te obrigue. Assim nunca vai ter que pensar se um nome é um ponteiro ou não. Se precisa de um ponteiro usa um asterisco. Já está resolvido desde o final dos anos 60 e usar isso dá uma certa paz de espírito. Nunca vai ter que pensar se PCX é um ponteiro para CX e ficar procurando a definição.

 

E como usaria uma coisa dessas?

 

Sem mexer muito, drawGraph() e as outras funções poderiam ser apenas:
 

void        drawGraph(Data*, Screen*);
int         populate(Data*);
void        show_points(Data*);

// funcoes de exemplo 

int         identidade(int);
int         z_exemplo(int);

 

E faz mais sentido. Quer mudar a tela? muda Screen. Que mudar a função? uma linha só.

 

populate()

 

Essa função faz o simples: cria e preenche o vetor de pontos. Só tirei daquela confusão de #define
 

int         populate(Data* S)
{
    S->x = (int*)malloc(S->points * sizeof(int));
    S->y = (int*)malloc(S->points * sizeof(int));
    int este_x = S->start; // o primeiro valor de x
    for (unsigned i = 0; i < S->points; i++, este_x += S->increment)
    {
        *(S->x + i) = este_x;
        *(S->y + i) = (*S->funcao)(este_x); // y = f(x)
    }
    return 0;
};

 

E veja como --- minha opinião --- fica muito mais simples: C foi escrita para isso. Você aloca os buffers, chama a função que o usuário do programa escolheu apenas chamando 

        *(S->y + i) = (*S->funcao)(este_x); // y = f(x)

 

E o sistema vai tabulando os pontos (x,y) n vetor.

 

show_points()

 

Claro que é preciso ter uma função dessas para mostrar os pontinhos na tela durante os testes, ao invés de desenhar coisas...

 

identidade()

 

int         identidade(int x)
{
    return x;
}

 

Claro que o primeiro teste seria com a meiga função identidade x = f(x)...

 

Como montar um conjunto de dados? Veja o seu exemplo completo como seria

 

int main(void)
{
    Screen tela1 =
    {
        .widthLine = 2,
        .lineStyle = PS_SOLID,
        .originX = 100,
        .originY = 100,
        .lenAbscisRL = 100,
        .lenOrdinRL = 100,
        .lineColor = redColor,
        .axisColor = whiteColor
    };

    Data    cjto1;
    cjto1.funcao = &z_exemplo;
    cjto1.start = -200;
    cjto1.increment = 10;
    cjto1.points = 800;
    populate(&cjto1);
    drawGraph(&cjto1, &tela1);
    free(cjto1.x);
    free(cjto1.y);
    printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
    return 0;
}

 

E a função

 

int         z_exemplo(int x)
{
    double seno = sin((double)x / 5.0);
    return (int)(50. * seno);
}

 

 

Só refatorei seu código para usar isso. 

 

Recomendo muito esquecer esse lance de desenhar na console. Use OpenGL ou qualquer framework. Você tem experiência. Não vai aprender nada com esse lado da API. Mas se está com falta de problemas e vai mesmo usar mude seu programa para Windows e o loop de mensagens e não rode na console nem dentro do IDE.

 

Eis o código todo em um programa só
 

/* Programa escrito por Lucca Rodrigues - 10/04/2021
Fonte: www.clubedohardware.com.br/forum/177-ccc
Acesso em: 14/04/2021 */

#define redColor    RGB(255,0,0)
#define blueColor   RGB(0,0,255)
#define greenColor  RGB(0,255,0)
#define whiteColor  RGB(255,255,255)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <windows.h>

typedef     int(FX)(int);

typedef     struct
{
    int     lenAbscisRL;
    int     lenOrdinRL;
    int     lineStyle;
    int     originX;
    int     originY;
    int     widthLine;
    COLORREF    lineColor;
    COLORREF    axisColor;

}   Screen;

typedef     struct
{
    unsigned    points;
    int*        x;
    int*        y;
    int         start;
    int         increment;
    FX*         funcao;

}   Data;


void        drawGraph(Data*, Screen*);
int         populate(Data*);
void        show_points(Data*);

// funcoes de exemplo 

int         identidade(int);
int         z_exemplo(int);


int main(void)
{
    Screen tela1 =
    {
        .widthLine = 2,
        .lineStyle = PS_SOLID,
        .originX = 100,
        .originY = 100,
        .lenAbscisRL = 100,
        .lenOrdinRL = 100,
        .lineColor = redColor,
        .axisColor = whiteColor
    };

    Data    cjto1;
    cjto1.funcao = &z_exemplo;
    cjto1.start = -200;
    cjto1.increment = 10;
    cjto1.points = 800;
    populate(&cjto1);
    //show_points(&cjto1);
    drawGraph(&cjto1, &tela1);
    free(cjto1.x);
    free(cjto1.y);
    printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
    return 0;
}

void        drawGraph(Data* D, Screen* S)
{
    HWND console = GetConsoleWindow();
    HDC hdc = GetDC(console);
    int sizeXY = D->points;
    for (int i = 0; i < sizeXY; i++)
    {
        if (D->x[i] >= 0 && D->y[i] >= 0)
        {   // Primeiro quadrante
            if ((D->x[i] > S->lenAbscisRL) || (D->y[i] > S->lenOrdinRL))
            {
                sizeXY--;
                for (int j = i; j < sizeXY; j++) {
                    D->x[j] = D->x[j + 1];
                    D->y[j] = D->y[j + 1];
                }
                i--;
            }
            else
            {
                D->x[i] += S->originX;
                D->y[i] = S->originY - D->y[i];
            }
        }
        else
        {
            if (D->x[i] <= 0 && D->y[i] >= 0)
            {   // Segundo quadrante
                if ((D->x[i] < -S->lenAbscisRL) || (D->y[i] > S->lenOrdinRL))
                {
                    sizeXY--;
                    for (int j = i; j < sizeXY; j++)
                    {
                        D->x[j] = D->x[j + 1];
                        D->y[j] = D->y[j + 1];
                    }
                    i--;
                }
                else
                {
                    D->x[i] = S->originX + D->x[i];
                    D->y[i] = S->originY - D->y[i];
                }
            }
            else
            {
                if (D->x[i] <= 0 && D->y[i] <= 0)
                {   // Terceiro quadrante
                    if ((D->x[i] < -S->lenAbscisRL) || (D->y[i] < -S->lenOrdinRL))
                    {
                        sizeXY--;
                        for (int j = i; j < sizeXY; j++)
                        {
                            D->x[j] = D->x[j + 1];
                            D->y[j] = D->y[j + 1];
                        }
                        i--;
                    }
                    else
                    {
                        D->x[i] = S->originX + D->x[i];
                        D->y[i] = S->originY - D->y[i];
                    }
                }
                else
                {
                    if (D->x[i] >= 0 && D->y[i] <= 0)
                    {   // Quarto quadrante
                        if ((D->x[i] > S->lenAbscisRL) || (D->y[i] < -S->lenOrdinRL))
                        {
                            sizeXY--;
                            for (int j = i; j < sizeXY; j++)
                            {
                                D->x[j] = D->x[j + 1];
                                D->y[j] = D->y[j + 1];
                            }
                            i--;
                        }
                        else
                        {
                            D->x[i] += S->originX;
                            D->y[i] = S->originY - D->y[i];
                        }
                    }
                }
            }
        }
    };  // for()

    SelectObject(hdc, CreatePen(S->lineStyle, S->widthLine, S->lineColor));

    for (int i = 0; i < (sizeXY - 1); i++)
    {
        MoveToEx(hdc, D->x[i], D->y[i], NULL);
        LineTo(hdc, D->x[i + 1], D->y[i + 1]);
    }

    SelectObject(hdc, CreatePen(PS_SOLID, 1, S->axisColor));

    MoveToEx(hdc, S->originX, S->originY - S->lenOrdinRL, NULL);
    LineTo(hdc, S->originX, S->originY + S->lenOrdinRL);

    MoveToEx(hdc, S->originX - S->lenAbscisRL, S->originY, NULL);
    LineTo(hdc, S->originX + S->lenAbscisRL, S->originY);

    ReleaseDC(console, hdc);

    return;
}


int         identidade(int x)
{
    return x;
}


int         populate(Data* S)
{
    S->x = (int*)malloc(S->points * sizeof(int));
    S->y = (int*)malloc(S->points * sizeof(int));
    int este_x = S->start; // o primeiro valor de x
    for (unsigned i = 0; i < S->points; i++, este_x += S->increment)
    {
        *(S->x + i) = este_x;
        *(S->y + i) = (*S->funcao)(este_x); // y = f(x)
    }
    return 0;
};


void        show_points(Data* D)
{
    printf("%zd pontos no total\n", D->points);
    for (unsigned i = 0; i < D->points; i += 1)
        printf("%d,%d\n", *(D->x + i), *(D->y + i));
    return;
};


int         z_exemplo(int x)
{
    double seno = sin((double)x / 5.0);
    return (int)(50. * seno);
}
// fim de main.c

 

 

Rodou seu programa fora do IDE? Fiquei curioso. Rodou no terminal do Windows?

 

 

 

  • Obrigado 1
  • Amei 1
Link para o comentário
Compartilhar em outros sites

Em 19/04/2021 às 23:33, arfneto disse:

Eu falava era da lógica que eu tentava entender... O lance do desenho é porque a primeira vez que precisar redesenhar a console o sistema vai perder a configuração que possibilitou o desenho, já que o programa terminou com um ReleaseDC().

...

Porque ao encerrar o programa o contexto é destruído.

Ah então é por isso? Nem tinha me dado conta :D

 

Em 19/04/2021 às 23:33, arfneto disse:

Uma definição para a tela podia ter sido
 


typedef     struct
{
    int     lenAbscisRL;
    int     lenOrdinRL;
    int     lineStyle;
    int     originX;
    int     originY;
    int     widthLine;
    COLORREF    lineColor;
    COLORREF    axisColor;

}   Screen;

 

E assim podia usar a mesma função com várias telas pra testar. Sem mexer no programa ou nos ONZE argumentos de drawGraph() :D

Estava pensando em fazer algo assim mais tarde, pra não ter que passar 300 argumentos na chamada da função, mas antes eu teria que ver que parâmetros realmente eram necessários. Desse jeito ficou 100% melhor.

 

Em 19/04/2021 às 23:33, arfneto disse:

Claro que o * poderia estar no typedef mas eu sempre vou recomendar: JAMAIS use um typedef para um ponteiro a menos que o cara que vai te dar a nota ou assinar o cheque te obrigue. Assim nunca vai ter que pensar se um nome é um ponteiro ou não. Se precisa de um ponteiro usa um asterisco. Já está resolvido desde o final dos anos 60 e usar isso dá uma certa paz de espírito. Nunca vai ter que pensar se PCX é um ponteiro para CX e ficar procurando a definição.

Pois é, isso é um pesadelo, e meu professor gosta bastante de usar isso aí 🤪

 

Em 19/04/2021 às 23:33, arfneto disse:

Recomendo muito esquecer esse lance de desenhar na console. Use OpenGL ou qualquer framework. Você tem experiência. Não vai aprender nada com esse lado da API. Mas se está com falta de problemas e vai mesmo usar mude seu programa para Windows e o loop de mensagens e não rode na console nem dentro do IDE.

Tudo bem.

Eu fiz esse programa aí porque vai fazer parte de outro, no qual eu iria plotar um gráfico da carga de um capacitor em função do tempo.

Essas coisas são só pra passar o tempo mesmo. No mais, programo porque tem uma nota envolvida, daí quando tenho um tempinho livre eu tento fazer alguma coisa bacana com o pouco que sei sobre C :D

 

De fato ficou 300% melhor o programa agora que você mexeu nele, certas coisas eu provavelmente não pensaria em fazer, faria no máximo o lance do formulário com struct.

 

Em 19/04/2021 às 23:33, arfneto disse:

Rodou seu programa fora do IDE? Fiquei curioso. Rodou no terminal do Windows?

Rodei apenas no IDE.

Link para o comentário
Compartilhar em outros sites

8 minutos atrás, Lucca Rodrigues disse:

Pois é, isso é um pesadelo, e meu professor gosta bastante de usar isso aí

 

A Microsoft usou isso por décadas, com LP isso LPW aquilo, mas no caso de um sistema assim grande as pessoas acabam se acostumando. Mas é 1d10t@. Se 
 

	Perfil     Area;

 

Então claro que
 

	Perfil*     xx = &Area;

 

xx é um ponteiro para Perfil. Escrever
 

    typedef		Perfil* PPerfil;

 

e declarar 

 

	PPerfil      xx = &Area;

 

realmente melhora algo? Ou só cria um símbolo, uma dúvida e uma chance de erro a mais? Além da suspeita de que todo tipo que comece por P deva ser checado duas ou 3 vezes? E o que dizer de 

 

	PPerfil*     yy = NULL;

 

Que é **Perfil mas sumiu na definição? Não seria melhor escrever

 

	Perfil**     yy = NULL;

 

Ou alguém ia querer declarar 

 

    typedef		PPerfil* PPPerfil;

 

Só pra poder declarar

 

    PPPerfil zz = &xx;

 

Ao invés de usar o simples asterisco, uma vez para cada nível de referência.

 

22 minutos atrás, Lucca Rodrigues disse:

Pois é, isso é um pesadelo, e meu professor gosta bastante de usar isso aí 

 

Ele provavelmente nunca trabalhou com um projeto de dezenas de milhares de linhas e centenas de estruturas...

 

24 minutos atrás, Lucca Rodrigues disse:

Essas coisas são só pra passar o tempo mesmo. No mais, programo porque tem uma nota envolvida, daí quando tenho um tempinho livre eu tento fazer alguma coisa bacana com o pouco que sei sobre C

 

No caso do plot, a lógica de Draw() pode ser mais simples se considerar os eixos como simples projeções dos valores (possivelmente double) das funções. Algo assim: nos seus eixos a unidade é pixel, linha. Seu gráfico vai ter por exemplo 1024 x 768 pixels. Se vai usar um intervalo entre 0 e 1 na sua função, para o eixo x, já sabe o que fazer: divida o intervalo, 1., pela variação de x, 1024. E calcule os valores para esses 1024 pontos.

 

Salve os y todos para ver a melhor escala na hora, pela variação estatística de y.

 

(Pode até desprezar alguns pontos extremos. Exemplo: dos 1024 pontos tem uma concentração deles, 95% entre 100 e 200. Mas tem uns pontos abaixo de -500 e uns 10 acima de 1000. Então pode não valer a pena usar o gráfico entre -1000,1000 para o y e seria muito melhor desprezar os 5% valores mais extremos.)

 

Definidos os 1024 y e a escala basta colocar os pontos no gráfico. 

 

32 minutos atrás, Lucca Rodrigues disse:

certas coisas eu provavelmente não pensaria em fazer, faria no máximo o lance do formulário com struct.

 

De longe o mais importante que eu mudei foi dar um fim no #define. É muito importante você entender isso nessas linguagens: você pode ter um vetor de funções, como outro vetor qualquer. Não deve usar macro. Se for assim cada usuário vai ter que ter a FONTE de seu programa para compilar TODA vez. Não faz sentido e ninguém vai usar isso. Apenas passa o endereço da função y=f(x). Não precisa do fonte do programa de plotting.

 

35 minutos atrás, Lucca Rodrigues disse:

Rodei apenas no IDE

 

Nunca faça isso. Deixe uns minutos para rodar fora dele, na console e no Terminal. Esse programa sequer funciona fora do IDE como viu nas telas que mostrei...

 

  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

Em 14/04/2021 às 22:10, Lucca Rodrigues disse:

Boa noite, colegas! :)

 

Introdução:

Recentemente tive a ideia de criar um programinha de console para plotar gráficos de Y em função de X com ajuda dos cabeçalhos math.h e windows.h, bastando inserir a equação como um dos argumentos de uma função (macro), obtendo assim as coordenadas que podem ser passadas para outra função responsável por plotar o gráfico.

 

Código:


#define redColor RGB(255,0,0)
#define blueColor RGB(0,0,255)
#define greenColor RGB(0,255,0)
#define whiteColor RGB(255,255,255)

#define getCoords(x, y, indxOp, xStartVal, xIncrm, pntsQtd, sizeXY, alocXY)     \
do {                                                                            \
    int alocAux = alocXY;                                                       \
    int xValAux = xStartVal;                                                    \
    for(int i = 0; i < pntsQtd; i++, xValAux+=xIncrm, sizeXY++){                \
        if(i >= (alocAux/sizeof(int))){                                         \
            alocAux += alocXY;                                                  \
            x = (int*)realloc(x, alocAux);                                      \
            y = (int*)realloc(y, alocAux);                                      \
        }                                                                       \
        x[i] = xValAux;                                                         \
        y[i] = indxOp;                                                          \
    }                                                                           \
} while (0)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <windows.h>
  
/* Programa escrito por Lucca Rodrigues - 10/04/2021
Fonte: www.clubedohardware.com.br/forum/177-ccc
Acesso em: 14/04/2021 */

void drawGraph(int *x, int *y, int sizeXY, int widthLine, int lineStyle, int originX,
               int originY, int lenAbscisRL, int lenOrdinRL, COLORREF lineColor, COLORREF axisColor){
    HWND console = GetConsoleWindow();
	HDC hdc = GetDC(console);

	for(int i = 0; i < sizeXY; i++){
        if(x[i] >= 0 && y[i] >= 0){
            // Primeiro quadrante
            if((x[i] > lenAbscisRL) || (y[i] > lenOrdinRL)){
                sizeXY--;
                for(int j = i; j < sizeXY; j++){
                    x[j] = x[j+1];
                    y[j] = y[j+1];
                }
                i--;
            } else{
                x[i] += originX;
                y[i] = originY - y[i];
            }
        } else if(x[i] <= 0 && y[i] >= 0){
            // Segundo quadrante
            if((x[i] < -lenAbscisRL) || (y[i] > lenOrdinRL)){
                sizeXY--;
                for(int j = i; j < sizeXY; j++){
                    x[j] = x[j+1];
                    y[j] = y[j+1];
                }
                i--;
            } else{
                x[i] = originX + x[i];
                y[i] = originY - y[i];
            }
        } else if(x[i] <= 0 && y[i] <= 0){
            // Terceiro quadrante
            if((x[i] < -lenAbscisRL) || (y[i] < -lenOrdinRL)){
                sizeXY--;
                for(int j = i; j < sizeXY; j++){
                    x[j] = x[j+1];
                    y[j] = y[j+1];
                }
                i--;
            } else{
                x[i] = originX + x[i];
                y[i] = originY - y[i];
            }
        } else if(x[i] >= 0 && y[i] <= 0){
            // Quarto quadrante
            if((x[i] > lenAbscisRL) || (y[i] < -lenOrdinRL)){
                sizeXY--;
                for(int j = i; j < sizeXY; j++){
                    x[j] = x[j+1];
                    y[j] = y[j+1];
                }
                i--;
            } else{
                x[i] += originX;
                y[i] = originY - y[i];
            }
        }


	}

	SelectObject(hdc, CreatePen(lineStyle, widthLine, lineColor));

    for(int i = 0; i < (sizeXY - 1); i++){
        MoveToEx(hdc, x[i], y[i], NULL);
        LineTo(hdc, x[i+1], y[i+1]);
    }

    SelectObject(hdc, CreatePen(PS_SOLID, 1, axisColor));

    MoveToEx(hdc, originX, originY-lenOrdinRL, NULL);
    LineTo(hdc, originX, originY+lenOrdinRL);

    MoveToEx(hdc, originX-lenAbscisRL, originY, NULL);
    LineTo(hdc, originX+lenAbscisRL, originY);

    ReleaseDC(console, hdc);

    return;
}

int main()
{
    int *x = (int*)malloc(10*sizeof(int));
    int *y = (int*)malloc(10*sizeof(int));
    int sizeXY = 0;

    //getCoords(x, y, indxOp, xStartVal, xIncrm, pntsQtd, sizeXY, alocXY)
    getCoords(x, y, 50.0f*sin((double)x[i]/5.0f), -200, 1, 400, sizeXY, 10*sizeof(int));

    //void drawGraph(int *x, int *y, int sizeXY, int widthLine, int lineStyle, int originX,
    //           int originY, int lenAbscisRL, int lenOrdinRL, COLORREF lineColor, COLORREF axisColor)
    drawGraph(x, y, sizeXY, 2, PS_SOLID, 100, 100, 100, 100, redColor, whiteColor);

    free(x);
    free(y);
    x = NULL;
    y = NULL;

    printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");

    return 0;
}

 

Para compilar no CodeBlocks é necessário fazer o seguinte: na área de management, clicar com o botão direito no projeto > Build options > Linker settings > Other linker options e digitar -lgdi32 > Ok.

Não testei o código no Visual Studio ainda.

 

Uma breve explicação:

Para desenhar o gráfico, estou usando as funções MoveToEx() e LineTo(), fora isso, só preciso inserir cada coordenada como num sistema de coordenadas cartesiano, que é o que parte da função drawGraph() faz. A outra parte é responsável por desenhar as linhas que compõem a forma de onda.

A macro getCoords é uma macro do tipo função, e um dos parâmetros dessa função é a equação que será atribuída a Y, e caso haja X nessa equação, o mesmo deverá ser passado indexado com um índice i (conforme o exemplo no código), já que as macros são substituídas por suas definições durante o tempo de compilação, e cada coordenada é referenciada por um índice.

Os parâmetros das funções eu tentei deixar autoexplicativos.

drawGraph():

  • int *x e int *y - ponteiros que apontarão para o mesmo local da memória que os ponteiros passados para a função;
  • int sizeXY - a quantidade de coordenadas de X e Y;
  • int widthLine - a espessura da linha da forma de onda;
  • int lineStyle - o estilo da linha (vide documentação a respeito de CreatePen());
  • int originX - a Origem do eixo X;
  • int originY - a Origem do eixo Y;
  • int lenAbscisRL - o comprimento da abscissa em ambas direções;
  • int lenOrdinRL - o comprimento da ordenada em ambas direções;
  • COLORREF lineColor - a coloração da linha da forma de onda;
  • COLORREF axisColor - a coloração das linhas que compõem o plano cartesiano.

getCoords()

  • x e y - ponteiros para armazenar as coordenadas;
  • indxOp - a equação, e se X estiver nela, o mesmo deverá estar indexado com um índice i;
  • xStartVal - um valor inicial que será atribuído à X;
  • xIncrm - o incremento que parte do valor inicial até atingir a quantidade total de pontos;
  • pntsQtd - quantidade total de pontos;
  • sizeXY - a quantidade de coordenadas de X e Y;
  • alocXY - o tamanho do bloco de memória que deve ser somado ao tamanho do bloco de memória apontado por x e y (se necessário).

 

Resultados:

Plotagem de uma senoide comum - função matemática:

image.png.0443cf327b43fd87b2cfd5803cc18957.png

Plotagem de uma onda quadrada - função matemática:

image.png.27bc165c733f1426d3743d268fe4701f.png

Plotagem de uma onda triangular - função matemática:

image.png.6c40e85bb1120c8db6a96b84d73603f8.png

Plotagem de um coração - 2 funções condicionadas:

image.png.c1db2f8023dc7c1b571ba7cc1c1a660d.png

Plotagem de... Algo bem estranho - função matemática:

image.png.9e5428fb4a1206ef4445d659e8541152.png

 

Algumas mudanças que pretendo fazer:

Muito bom, não sabia que tinha todo este potencial! Parabéns!!

Em 14/04/2021 às 22:10, Lucca Rodrigues disse:

 

  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisa ser um usuário para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...