소연의_개발일지
article thumbnail

목차

  • 개발환경
  • 개발완성보고서
    • 요구사항분석서
    • 개발 일정표
    • DB구조도
    • 프로그램 구조도
  • 프로젝트 발표 PPT 자료
  • 소스 코드
  • 프로그램 시연
  • 첨부 파일

 


개발환경

  • 운영체제: Window 10 64 bit
  • 개발언어: Python 3.11, C#, JavaScript
  • 개발 툴: Pycharm, Visual Studio 2022
  • DBSM: PostgreSQL
  • 추가 패키지: Pandas, Matplotlib, Npgsql, Newtonsoft.Json, JavaScirptEngineSwicher.Core

개발완성보고서


요구사항분석서


개발 일정표

 

데이터베이스 구조도

 


ERD


프로그램 작동 순서도


프로젝트 발표 PPT 자료

 

 

 


소스 코드

StartForm.cs

using System;
using System.Windows.Forms;

namespace FranchiseProject
{
    public partial class StartForm : Form
    {
        public StartForm()
        {
            InitializeComponent();
        }

        private void ScreenTransition(object sender, MouseEventArgs e)
        {
            MainForm MainForm = new MainForm();
            MainForm.Show();
            this.Hide();
        }

        private void pictureboxClicked(object sender, EventArgs e)
        {
            MainForm MainForm = new MainForm();
            MainForm.Show();
            this.Hide();
        }
    }

MainForm.cs

더보기
//using static System.Windows.Forms.VisualStyles.VisualStyleElement;
//using Newtonsoft.Json;
//using System.Diagnostics;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web.Script.Serialization;
using System.Windows.Forms;

namespace FranchiseProject
{
    public partial class MainForm : Form
    {
        // 지역명, 위도, 경도  (ex. "문정동", "37.412412", "124.512512")
        List<Tuple<string, double, double>> tuples = new List<Tuple<string, double, double>>();

        // DB 불러오기
        private const string ConnectionString = "Host=10.10.20.103;Username=postgres;Password=1234;Database=franchise";

        // 검색 버튼 클릭 됐는지 감지
        bool clickDetected = false;

        // 마우스 드래그를 위한 offset 변수
        private Point offset;

        // 전역 변수
        private string minCostValue_;
        private string maxCostValue_;
        private string salesIncome_;
        private string salesPeople_;
        private string facilityCnt_;
        private string resultRate_;
        private string resultCnt_;
        private string resultCompete_;

        // 생성자
        public MainForm()
        {
            InitializeComponent();
            InitializeComboBoxes();
        }

        // 지도
        private void MainForm_Load(object sender, EventArgs e)
        {
            SetFontList();
            // 로드될 때 생성
            // WebBrowser 컨트롤에 "kakaoMap.html" 을 표시한다. 
            Version ver = webBrowser1.Version;
            string name = webBrowser1.ProductName;
            string str = webBrowser1.ProductVersion;
            string html = "kakaoMap.html";
            string dir = Directory.GetCurrentDirectory();
            string path = Path.Combine(dir, html);
            webBrowser1.Navigate(path);
            SetFontList();
        }

        // 메인폼 테두리 설정
        private void MainForm_Paint(object sender, PaintEventArgs e)
        {
            ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle,
                Color.Black, 2, ButtonBorderStyle.Solid,
                Color.Black, 2, ButtonBorderStyle.Solid,
                Color.Black, 2, ButtonBorderStyle.Solid,
                Color.Black, 2, ButtonBorderStyle.Solid);
        }

        // 폰트 불러오는 함수
        public static Font FontLoad(int fontNum, int fontSize)
        {
            string[] fontPaths = { @"font\Pretendard-Regular.ttf", @"font\Maplestory_Bold.ttf" };
            string baseDirectory = Directory.GetParent(Environment.CurrentDirectory).Parent.FullName;

            using (PrivateFontCollection privateFonts = new PrivateFontCollection())
            {
                foreach (string fontPath in fontPaths)
                {
                    string fontFilePath = Path.Combine(baseDirectory, fontPath);
                    privateFonts.AddFontFile(fontFilePath);
                }

                if (fontNum >= 0 && fontNum < privateFonts.Families.Length)
                {
                    return new Font(privateFonts.Families[fontNum], fontSize);
                }

                else
                {
                    return SystemFonts.DefaultFont;
                }
            }
        }
        public void SetFont(Control control, int fontNum, int fontSize)
        {
            control.Font = FontLoad(fontNum, fontSize);
        }
        // DB
        // 특정 테이블에서 특정 칼럼의 값을 반환하는 함수
        public static List<string> GetValuesFromTable(string tableName, string columnName, string criteria = null, bool distinct = false)
        {
            List<string> results = new List<string>();

            using (var connection = new NpgsqlConnection(ConnectionString))
            {
                connection.Open();

                string distinctClause = distinct ? "DISTINCT" : ""; // distinct 값에 따라 쿼리 조각 결정
                string query = $"SELECT {distinctClause} \"{columnName}\" FROM \"{tableName}\"";

                if (!string.IsNullOrWhiteSpace(criteria))
                {
                    query += $" WHERE {criteria}";
                }

                using (var cmd = new NpgsqlCommand(query, connection))
                {
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            results.Add(reader.GetString(0));
                        }
                    }
                }
            }

            return results;
        }
        
        // 특정 테이블의 여러 컬럼 값을 반환할 
        public static List<Dictionary<string, object>> GetValuesFromMultipleColumns(string tableName, List<string> columnNames, string criteria = null, bool distinct = false)
        {
            List<Dictionary<string, object>> results = new List<Dictionary<string, object>>();

            using (var connection = new NpgsqlConnection(ConnectionString))
            {
                connection.Open();

                string columns = string.Join(", ", columnNames.Select(c => $"\"{c}\""));
                string distinctClause = distinct ? "DISTINCT" : ""; // distinct 값에 따라 쿼리 조각 결정
                string query = $"SELECT {distinctClause} {columns} FROM \"{tableName}\"";

                if (!string.IsNullOrWhiteSpace(criteria))
                {
                    query += $" WHERE {criteria}";
                }
                Console.WriteLine(query);
                using (var cmd = new NpgsqlCommand(query, connection))
                {
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            var row = new Dictionary<string, object>();
                            for (int i = 0; i < reader.FieldCount; i++)
                            {
                                row[reader.GetName(i)] = reader.GetValue(i);
                            }
                            results.Add(row);
                        }
                    }
                }
            }

            return results;
        }
        
        // 특정 테이블의 모든 행의 값을 반환하는 함수
        public static List<Dictionary<string, object>> GetAllRowsFromTable(string tableName, string criteria = null)
        {
            List<Dictionary<string, object>> results = new List<Dictionary<string, object>>();

            using (var connection = new NpgsqlConnection(ConnectionString))
            {
                connection.Open();

                string query = $"SELECT * FROM \"{tableName}\"";

                if (!string.IsNullOrWhiteSpace(criteria))
                {
                    query += $" WHERE {criteria}";
                }

                using (var cmd = new NpgsqlCommand(query, connection))
                {
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            var row = new Dictionary<string, object>();
                            for (int i = 0; i < reader.FieldCount; i++)
                            {
                                row[reader.GetName(i)] = reader.GetValue(i);
                            }
                            results.Add(row);
                        }
                    }
                }
            }

            return results;
        }
        // 콤보박스
        private void InitializeComboBoxes()
        {
            //콤보박스
            string[] data = { "북구", "서구", "동구", "남구", "광산구" };
            flatComboBox1.Items.Add("선택");
            flatComboBox1.Items.AddRange(data); // 콤보박스에 자료 넣기
            flatComboBox1.SelectedIndex = 0; // 첫번째 아이템 선택
        }

        //private void comboBox1_SelectedIndexChanged_1(object sender, EventArgs e)
        //{
        //    // 첫 번째 콤보박스의 선택에 따라 두 번째 콤보박스의 항목을 설정
        //    string selectedGu = comboBox1.SelectedItem.ToString();
        //    update_combobox2(selectedGu);
        //    comboBox2.SelectedIndex = 0;
        //}

        private void flatComboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            // 첫 번째 콤보박스의 선택에 따라 두 번째 콤보박스의 항목을 설정
            string selectedGu = flatComboBox1.SelectedItem.ToString();
            UpdateComboBox2(selectedGu);
            flatComboBox2.SelectedIndex = 0;
        }

        private void UpdateComboBox2(string guName)
        {
            // 두 번째 콤보박스의 항목을 초기화
            flatComboBox2.Items.Clear();
            List<string> DongNames = GetValuesFromTable("TB_DONG", "H_DONG_NAME", $"\"GU_NAME\" = '{guName}' ORDER BY \"H_DONG_NAME\"", true);
            flatComboBox2.Items.Add("선택");
            foreach (string dong in DongNames)
            {
                flatComboBox2.Items.Add(dong); // ComboBox에 d를 추가합니다.
            }
        }
        private void UpdateTabPage(string guName, string dongName)
        {
            // 현재 comboBox2에서 선택된 동(Dong) 이름을 가져옴
            string dong = flatComboBox2.Text;

            // DB로부터 가져올 칼럼 이름들을 리스트로 정의
            var columns = new List<string> { "DEAL_TYPE", "DEAL_USE", "DEAL_GU", "DEAL_DONG", "DEAL_ADDR", "DEAL_DEPOSIT", "DEAL_PRICE", "DEAL_RENT_PRICE", "DEAL_SPACE" };

            // DB로부터 지정된 조건의 레코드들을 가져옴
            var data = GetValuesFromMultipleColumns("TB_DEAL", columns, $"\"DEAL_DONG\" = '{dongName}'");

            // 기존 리스트 뷰 아이템을 모두 지움
            listView1.Items.Clear();
            listView2.Items.Clear();

            // 가져온 각 행(레코드)에 대해 아래의 작업을 수행
            foreach (var row in data)
            {
                // 해당 행에서 필요한 데이터를 가져옴
                string dealType = row["DEAL_TYPE"].ToString();
                string dealUse = row["DEAL_USE"].ToString();
                string dealDeposit = row["DEAL_DEPOSIT"].ToString();
                string dealPrice = row["DEAL_PRICE"].ToString();
                string dealRentprice = row["DEAL_RENT_PRICE"].ToString();
                string dealSpace = row["DEAL_SPACE"].ToString();

                // 거래 유형이 '매매'인 경우
                if (dealType == "매매")
                {
                    // listView1에 해당 아이템을 추가
                    var item = new ListViewItem(dealType);
                    item.SubItems.Add($"{dealUse}");
                    item.SubItems.Add($"{dealPrice}");
                    item.SubItems.Add($"{dealSpace}");
                    listView1.Items.Add(item);
                }

                // 거래 유형이 '월세'인 경우
                else if (dealType == "월세")
                {
                    // listView2에 해당 아이템을 추가
                    var item = new ListViewItem(dealType);
                    item.SubItems.Add($"{dealUse}");
                    item.SubItems.Add($"{dealDeposit}");
                    item.SubItems.Add($"{dealRentprice}");
                    item.SubItems.Add($"{dealSpace}");
                    listView2.Items.Add(item);
                }

                // 출력 확인용: 현재 행의 모든 열(칼럼) 데이터를 콘솔에 출력
                foreach (var keyValuePair in row)
                {
                    Console.WriteLine($"{keyValuePair.Key}: {keyValuePair.Value}");
                }
            }
        }
        private void UpdatePictureBox(string guName, string dongName)
        {
            // 필요한 열 이름들을 리스트에 저장
            var columns = new List<string> { "GU_NAME", "H_DONG_NAME" };

            // GetValuesFromMultipleColumns 메서드를 사용하여 데이터베이스에서 해당 조건에 맞는 데이터를 가져옴
            var data = GetValuesFromMultipleColumns("TB_DONG", columns, $" \"GU_NAME\" = '{guName}' and \"H_DONG_NAME\" = '{dongName}' ");

            // 가져온 데이터가 없으면 함수를 종료
            if (data.Count == 0)
            {
                return;
            }

            // 첫 번째 행의 데이터를 가져옴
            var row = data[0];
            string dongName_ = row["H_DONG_NAME"].ToString();
            string guName_ = row["GU_NAME"].ToString();

            // 이미지 파일들이 저장된 폴더 경로들을 배열에 저장
            string[] folderNames = {
                @"graph\00_동별_다중이용시설",
                @"graph\01_동별_인구비율",
                @"graph\02_동별_면적범위별_평균보증금_임대료_pastel",
                @"graph\03_구별_1030인구대비_월평균추정매출",
                @"graph\04_구별_월평균추정매출_경쟁업체",
                @"graph\05_전역_광주광역시_화장품상가_분포도"
            };

            string currentDirectory = Directory.GetParent(Environment.CurrentDirectory).Parent.FullName;

            // 각 폴더에서 해당 구와 동 이름을 포함하는 이미지 파일들의 경로를 가져옴
            for (int idx = 0; idx < folderNames.Length; idx++)
            {
                string folderPath = Path.Combine(currentDirectory, folderNames[idx]);
                List<string> imageFiles = GetMatchingImageFiles(folderPath, guName, dongName);

                // 이미지 파일이 없는 경우 none_data 이미지로 설정
                if (imageFiles.Count == 0)
                {
                    string noneDataImagePath = Path.Combine(folderPath, "none_data.png");
                    Image noneDataImage = Image.FromFile(noneDataImagePath);

                    // 현재 폴더 인덱스에 따라 해당 PictureBox에 none_data를 설정
                    if (idx == 0)
                    {
                        facPictureBox.Image = noneDataImage;
                    }
                    else if (idx == 1)
                    {
                        popPictureBox.Image = noneDataImage;
                    }
                    else if (idx == 2)
                    {
                        pricePictureBox.Image = noneDataImage;
                    }
                    else if (idx == 3)
                    {
                        guPictureBox1.Image = noneDataImage;
                    }
                    else if (idx == 4)
                    {
                        guPictureBox2.Image = noneDataImage;
                    }
                }
                else
                {
                    // 이미지 파일이 있는 경우, 해당 이미지를 설정
                    string imagePath = imageFiles[0];
                    Image image = Image.FromFile(imagePath);

                    // 현재 폴더 인덱스에 따라 해당 PictureBox에 이미지를 설정
                    if (idx == 0)
                    {
                        facPictureBox.Image = image;
                    }
                    else if (idx == 1)
                    {
                        popPictureBox.Image = image;
                    }
                    else if (idx == 2)
                    {
                        pricePictureBox.Image = image;
                    }
                    else if (idx == 3)
                    {
                        guPictureBox1.Image = image;
                    }
                    else if (idx == 4)
                    {
                        guPictureBox2.Image = image;
                    }
                }

                // 광주광역시 전체 데이터는 직접 경로와 파일명을 가져와서 pictureBox에 저장
                rivalPictureBox1.Image = Image.FromFile(GetImagePath(folderNames[5], $"광주광역시_전체상가_지도.png"));
                rivalPictureBox2.Image = Image.FromFile(GetImagePath(folderNames[5], $"광주광역시_화장품상가_지도.png"));
            }
        }

        // 주어진 폴더 경로와 파일 이름을 기반으로 이미지 파일의 전체 경로를 생성하는 함수
        private string GetImagePath(string folderPath, string fileName)
        {
            // 상대 경로를 가져옴
            string currentDirectory = Directory.GetParent(Environment.CurrentDirectory).Parent.FullName;

            // Path.Combine 메서드를 사용하여 현재 디렉토리, 폴더 경로, 파일 이름을 결합하여 이미지 파일의 전체 경로를 반환함
            return Path.Combine(currentDirectory, folderPath, fileName);
        }
        // 주어진 기본 폴더 경로(baseFolderPath), 구이름(guName), 동이름(dongName)에 맞는 이미지 파일들을 찾아서 리스트로 반환
        private List<string> GetMatchingImageFiles(string baseFolderPath, string guName, string dongName)
        {
            // 결과로 반환할 이미지 파일들을 담을 리스트 생성
            List<string> imageFiles = new List<string>();

            // 지정된 폴더 경로에서 특정 패턴에 맞는 파일들을 찾아옴 ('구이름_동이름.*' 형태)
            string[] filesWithDong = Directory.GetFiles(baseFolderPath, $"{guName}_{dongName}.*");
            string[] filesWithoutDong = Directory.GetFiles(baseFolderPath, $"{guName}.*");

            // '구이름_동이름.*' 형태의 파일이 없을 경우 '구이름.*' 형태의 파일들을 가져옴
            if (filesWithDong.Length == 0)
            {
                imageFiles.AddRange(filesWithoutDong);
            }

            else
            {
                // '구이름_동이름.*' 형태의 파일이 있을 경우 해당 파일들을 반환할 리스트에 추가
                imageFiles.AddRange(filesWithDong);
            }

            // 결과 리스트 반환
            return imageFiles;
        }
        // 지역 검색
        public void Search(string area)
        {
            // 요청을 보낼 url 
            string site = "https://dapi.kakao.com/v2/local/search/address.json";
            string query = string.Format("{0}?query={1}", site, area);
            WebRequest request = WebRequest.Create(query); // 요청 생성. 
            string apiKey = "106e805bafc9548f37b878db306c0484"; // API 인증키 입력. (각자 발급한 API 인증키를 입력하자)
            string header = "KakaoAK " + apiKey;


            request.Headers.Add("Authorization", header); // HTTP 헤더 "Authorization" 에 header 값 설정. 
            WebResponse response = request.GetResponse(); // 요청을 보내고 응답 객체를 받는다. 
            Stream stream = response.GetResponseStream(); // 응답객체의 결과물


            StreamReader reader = new StreamReader(stream, Encoding.UTF8);
            String json = reader.ReadToEnd(); // JOSN 포멧 문자열
            //Console.WriteLine("결과물" + json);

            JavaScriptSerializer js = new JavaScriptSerializer(); // (Reference 에 System.Web.Extensions.dll 을 추가해야한다)
            var dob = js.Deserialize<dynamic>(json);

            var docs = dob["documents"];
            object[] buf = docs;
            int length = buf.Length;

            for (int i = 0; i < length; i++) // 지역명, 위도, 경도 읽어오기. 
            {
                string addressName = docs[i]["address_name"];
                double x = double.Parse(docs[i]["x"]); // 위도
                double y = double.Parse(docs[i]["y"]); // 경도
                tuples.Add(new Tuple<string, double, double>(addressName, x, y));
                Console.WriteLine("저장한주소값: " + addressName + x + y);
            }
        }
        // 지도 확대
        private void ZoomInMap(object sender, EventArgs e)
        {
            webBrowser1.Document.InvokeScript("zoomIn"); // 줌인
        }

        // 지도 축소
        private void ZoomOutMap(object sender, EventArgs e)
        {
            webBrowser1.Document.InvokeScript("zoomOut"); // 줌아웃
        }
        // 검색 버튼 눌렀을 때 연결
        private void SearchButtonClick(object sender, EventArgs e)
        {
            tabControl1.SelectedIndex = 0;
            SetFontList();
            //정보 불러오기
            tuples.Clear();
            string gu = flatComboBox1.Text;
            string dong = flatComboBox2.Text;
            string newAddr = "광주광역시 " + gu + dong;

            if (gu == "선택" | dong == "선택")
            {
                MessageBox.Show("구와 동을 선택해주세요!", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            // 클릭 됐는지 감지
            clickDetected = true;

            // 튜플에 값 넣기
            Search(newAddr);
            var sel = tuples[0];

            // 위도, 경도 불러와서 이동
            object[] arr = new object[] { sel.Item3, sel.Item2 }; // 위도, 경도
            object res = webBrowser1.Document.InvokeScript("panTo", arr);
            UpdateTabPage(gu, dong);
            UpdatePictureBox(gu, dong);

            // 올리브영 위치 찍기
            var columns = new List<string> { "LOC_NAME", "LOC_ADDR", "LOC_X", "LOC_Y" };
            var condition = $"\"LOC_GU\" = '{gu}' AND \"LOC_DONG\" = \'{dong}\'";

            var data = GetValuesFromMultipleColumns("TB_LOCATION", columns, condition, false);
            StringBuilder jsCode = new StringBuilder();
            jsCode.AppendLine($"remove_markers('olive_young');");

            SetFontList();

            if (data != null && data.Count > 0)
            {
                jsCode.AppendLine($"add_markers('olive_young', [");
                foreach (var row in data)
                {
                    string name = row["LOC_NAME"].ToString(); // 업체명
                    string addr = row["LOC_ADDR"].ToString();  // 주소 
                    string x = row["LOC_X"].ToString(); //x좌표
                    string y = row["LOC_Y"].ToString(); //y좌표
                    Console.WriteLine(name + addr + x + y); // 확인용

                    // 각 시설의 정보를 바탕으로 JavaScript 코드를 추가
                    jsCode.AppendLine($"{{ title: '{name}', addr: '{addr}', latlng: new kakao.maps.LatLng({x}, {y}) }},");
                }
                jsCode.AppendLine("]);");
                Console.WriteLine(jsCode.ToString());

                // 생성된 JavaScript 코드를 웹 브라우저 컨트롤을 통해 실행
                webBrowser1.Document.InvokeScript("eval", new object[] { jsCode.ToString() });
            }

            // 버스 위치 찍기
            var busColumns = new List<string> { "BUS_NAME", "BUS_ADDR", "BUS_X", "BUS_Y" };
            var busCondition = $"\"BUS_GU\" = '{gu}' AND \"BUS_DONG\" = \'{dong}\'";

            var busData = GetValuesFromMultipleColumns("TB_BUS", busColumns, busCondition, false);
            StringBuilder busJsCode = new StringBuilder();
            busJsCode.AppendLine($"remove_markers('bus');");

            if (busData != null && busData.Count > 0)
            {
                busJsCode.AppendLine($"add_markers('bus', [");
                foreach (var row in busData)
                {
                    string name = row["BUS_NAME"].ToString(); // 업체명
                    string addr = row["BUS_ADDR"].ToString();  // 주소 
                    string x = row["BUS_X"].ToString(); //x좌표
                    string y = row["BUS_Y"].ToString(); //y좌표
                    Console.WriteLine(name + addr + x + y); // 확인용

                    // 각 시설의 정보를 바탕으로 JavaScript 코드를 추가
                    busJsCode.AppendLine($"{{ title: '{name}', addr: '{addr}', latlng: new kakao.maps.LatLng({x}, {y}) }},");
                }
                busJsCode.AppendLine("]);");
                Console.WriteLine(busJsCode.ToString());

                // 생성된 JavaScript 코드를 웹 브라우저 컨트롤을 통해 실행
                webBrowser1.Document.InvokeScript("eval", new object[] { busJsCode.ToString() });
            }

            SetFontList();

            //예상 창업 비용 작업 완
            var columnsDeal = new List<string> { "DEAL_DEPOSIT", "DEAL_RENT_PRICE", "DEAL_SPACE" };
            var conditionDeal = $"\"DEAL_TYPE\" = \'월세\' and \"DEAL_DONG\" = \'{dong}\'";
            var dataDeal = GetValuesFromMultipleColumns("TB_DEAL", columnsDeal, conditionDeal, false);
            int franchiseCost = 1100; // 가맹비
            int premium = 10000; // 권리금
            int furniture = 13000; // 집기비용
            int systemCost = 1000; // 전산비용
            int startGoods = 10000; // 초도상품구매비용
            int workCost = 1200; // 공사비
            int etc = 200; // 기타
            int deposit = 0; // 보증금
            int rentPrice = 0; // 임대료
            float space = 0; // 면적
            int interiorCost = 0; // 인테리어비 면적 // 3.3 * 198

            List<int> depositList = new List<int>();
            List<int> interiorList = new List<int>();
            foreach (var row in dataDeal)
            {
                deposit = Convert.ToInt32(row["DEAL_DEPOSIT"]);
                rentPrice = Convert.ToInt32(row["DEAL_RENT_PRICE"]);
                space = Convert.ToSingle(row["DEAL_SPACE"]);
                double result = space / 3.3 * 198;
                interiorCost = Convert.ToInt32(result);
                depositList.Add(deposit);
                interiorList.Add(interiorCost);
            }

            int minDeposit = depositList.Count > 0 ? depositList.Min() : 0;
            int maxDeposit = depositList.Count > 0 ? depositList.Max() : 0;
            int minInteriorCost = interiorList.Count > 0 ? interiorList.Min() : 0;
            int maxInteriorCost = interiorList.Count > 0 ? interiorList.Max() : 0;

            int totalMin = franchiseCost + premium + furniture + systemCost + startGoods + workCost + etc + minDeposit + minInteriorCost;
            int totalMax = franchiseCost + premium + furniture + systemCost + startGoods + workCost + etc + maxDeposit + maxInteriorCost;

            string minCost = FormatWon(totalMin); // 최종 최소 금액
            string maxCost = FormatWon(totalMax); // 최종 최고 금액

            // ↓ 월평균매출, 유동인구 로직
            string condition1 = $"\"FACILITY_GU\" = '{gu}' AND \"FACILITY_DONG\" = '{dong}'";
            var faciltiyData = GetAllRowsFromTable("TB_FACILITY", condition1);
            int facilityCnt = faciltiyData.Count(); // 다중이용시설 갯수

            var salsesColumns = new List<string> { "SALES_INCOME", "SALES_PEOPLE" };
            var salesCon = $"\"SALES_GU\" = '{gu}' AND \"SALES_DONG\" = '{dong}'";
            var salesData = GetValuesFromMultipleColumns("TB_SALES", salsesColumns, salesCon, false);

            // 월평균매출, 유동인구
            string salesIncome = salesData[0]["SALES_INCOME"].ToString(); // 월평균매출 
            string salesPeople = salesData[0]["SALES_PEOPLE"].ToString(); // 유동인구

            // 데이터 정규화 및 추천 기능 테이블
            // List<string> Result = GetValuesFromTable("TB_RESULT", "RESULT_RATE", $"\"RESULT_GU\" = '{gu}' AND \"RESULT_DONG\" = '{dong}'", false);
            var resultColumns = new List<string> { "RESULT_RATE", "RESULT_CNT" };
            var result_ = GetValuesFromMultipleColumns("TB_RESULT", resultColumns, $"\"RESULT_GU\" = '{gu}' AND \"RESULT_DONG\" = '{dong}'", false);
            string resultRate = result_[0]["RESULT_RATE"].ToString();
            string resultCnt = result_[0]["RESULT_CNT"].ToString(); // 해당동의 올리브영 갯수

            // 경쟁업체 수 조회
            var competeColumns = new List<string> { "SALES_COMPETE" };
            var competeQuery = GetValuesFromMultipleColumns("TB_SALES", competeColumns, $"\"SALES_GU\" = '{gu}' AND \"SALES_DONG\" = '{dong}'", false);
            string resultCompete = competeQuery[0]["SALES_COMPETE"].ToString();

            // 전역 변수에 할당
            minCostValue_ = minCost;
            maxCostValue_ = maxCost;
            salesIncome_ = salesIncome;
            salesPeople_ = salesPeople;
            facilityCnt_ = facilityCnt.ToString();
            resultRate_ = resultRate;
            resultCnt_ = resultCnt;
            resultCompete_ = resultCompete;

            // 체크박스 비활성화 설정
            checkBox1.Checked = false;
            checkBox2.Checked = false;
            checkBox3.Checked = false;
            checkBox4.Checked = false;
            checkBox5.Checked = false;
            checkBox6.Checked = false;
            checkBox7.Checked = false;
            checkBox8.Checked = false;
            checkBox9.Checked = false;
            checkBox10.Checked = false;
            SetFontList();
        }
        // ~억 ~만원 이라고 표현해주는 함수
        static string FormatWon(int price)
        {
            int eok = price / 10000;
            int man = (price / 10);

            return $"{eok}억 {man}만원";
        }

        // 체크박스의 상태(선택/해제)에 따라 지도 상에 마커를 표시하거나 삭제
        private void ShowCheckBoxMarkers(CheckBox checkBox)
        {
            List<Dictionary<string, object>> facility_rows = GetFacilitiesByTypeAndLocation(checkBox, flatComboBox1, flatComboBox2);

            StringBuilder jsCode = new StringBuilder(); // JavaScript 코드를 동적으로 생성하기 위한 StringBuilder
            string facilityType = checkBox.Tag.ToString();  //체크박스의 태그 값을 사용하여 시설 유형을 가져옴 -> ui에서 수정함

            // 체크박스 선택되었을 때
            if (checkBox.Checked)
            {
                jsCode.AppendLine($"add_markers('{facilityType}', [");
                foreach (var row in facility_rows)
                {
                    string name = row["FACILITY_NAME"].ToString(); // 업체명
                    string addr = row["FACILITY_ADDR"].ToString();  // 주소 
                    string x = row["FACILITY_X"].ToString(); //x좌표
                    string y = row["FACILITY_Y"].ToString(); //y좌표
                    Console.WriteLine(name + addr + x + y); // 확인용

                    // // 각 시설의 정보를 바탕으로 JavaScript 코드를 추가
                    jsCode.AppendLine($"{{ title: '{name}', addr: '{addr}', latlng: new kakao.maps.LatLng({x}, {y}) }},");
                }
                jsCode.AppendLine("]);");
            }
            else // 체크박스 해제되었을 때 마커 삭제 명령 이동
            {
                jsCode.AppendLine($"remove_markers('{facilityType}');");
            }

            // 생성된 JavaScript 코드를 웹 브라우저 컨트롤을 통해 실행
            webBrowser1.Document.InvokeScript("eval", new object[] { jsCode.ToString() });
        }

        // 체크박스의 이름을 참조해서 db에서 값을 가져온다.(인자: 체크박스, 구 콤보박스, 동 콤보박스)
        private List<Dictionary<string, object>> GetFacilitiesByTypeAndLocation(CheckBox checkBox, ComboBox guComboBox, ComboBox dongComboBox)
        {
            // 데이터 가져옴
            string facilityType = checkBox.Tag.ToString(); // Tag에서 시설 타입 가져오기
            string gu = guComboBox.Text;
            string dong = dongComboBox.Text;
            string condition = "";

            // 편의시설 합친 것 때문에 수정해줌
            if (facilityType == "음식점")
            {
                condition = $"\"FACILITY_GU\" = '{gu}' AND \"FACILITY_DONG\" = '{dong}' AND \"FACILITY_TYPE\" IN ('음식점', '패스트푸드', '피자', '제빵', '음식점', '치킨', '분식', '술집')";
            }
            else if (facilityType == "쇼핑몰")
            {
                condition = $"\"FACILITY_GU\" = '{gu}' AND \"FACILITY_DONG\" = '{dong}' AND \"FACILITY_TYPE\" IN ('쇼핑몰', '할인점')";
            }
            else if (facilityType == "중고등학교")
            {
                condition = $"\"FACILITY_GU\" = '{gu}' AND \"FACILITY_DONG\" = '{dong}' AND \"FACILITY_TYPE\" IN ('중학교', '고등학교')";
            }
            else if (facilityType == "문화시설")
            {
                condition = $"\"FACILITY_GU\" = '{gu}' AND \"FACILITY_DONG\" = '{dong}' AND \"FACILITY_TYPE\" IN ('문화시설', '영화관')";
            }
            else
            {
                condition = $"\"FACILITY_GU\" = '{gu}' AND \"FACILITY_DONG\" = '{dong}' AND \"FACILITY_TYPE\" = '{facilityType}'";
            }

            return GetAllRowsFromTable("TB_FACILITY", condition);
        }
        private void resultButton_Click(object sender, EventArgs e)
        {
            if (flatComboBox1.Text == "선택" || flatComboBox2.Text == "선택")
            {
                MessageBox.Show("구와 동을 선택해주세요!", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }

            else if (clickDetected == false)
            {
                MessageBox.Show("검색 버튼을 클릭해주세요!", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }

            else
            {
                string guName = flatComboBox1.Text;
                string dongName = flatComboBox2.Text;

                DialogForm dialogForm = new DialogForm(guName, dongName, minCostValue_, maxCostValue_, salesIncome_, salesPeople_, facilityCnt_, resultRate_, resultCnt_, resultCompete_);
                dialogForm.ShowDialog();
            }

        }

        private void exitButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;
        }

        // 다중이용시설 체크박스 이벤트 연결
        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            //편의점
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void checkBox2_CheckedChanged(object sender, EventArgs e)
        {
            //카페
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void checkBox3_CheckedChanged(object sender, EventArgs e)
        {
            //은행
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void checkBox4_CheckedChanged(object sender, EventArgs e)
        {
            //쇼핑몰
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void checkBox5_CheckedChanged(object sender, EventArgs e)
        {
            //병원
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void checkBox6_CheckedChanged(object sender, EventArgs e)
        {
            //음식점
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void checkBox7_CheckedChanged(object sender, EventArgs e)
        {
            //공용주차장
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void checkBox8_CheckedChanged(object sender, EventArgs e)
        {
            // 중 고등학교
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void checkBox9_CheckedChanged(object sender, EventArgs e)
        {
            // 대학교
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void checkBox10_CheckedChanged(object sender, EventArgs e)
        {
            //문화시설
            ShowCheckBoxMarkers(sender as CheckBox);
        }

        private void MainForm_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                offset = new Point(e.X, e.Y);
            }
        }

        private void MainForm_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point newLocation = this.Location;
                newLocation.X += e.X - offset.X;
                newLocation.Y += e.Y - offset.Y;
                this.Location = newLocation;
            }
        }

        private void label2_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                offset = new Point(e.X, e.Y);
            }
        }

        private void label2_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point newLocation = this.Location;
                newLocation.X += e.X - offset.X;
                newLocation.Y += e.Y - offset.Y;
                this.Location = newLocation;
            }
        }
    }
}

DialogForm.cs

using System;
using System.Drawing;
using System.Windows.Forms;

namespace FranchiseProject
{
    public partial class DialogForm : Form
    {
        MainForm mainForm = new MainForm();

        // 마우스 드래그를 위한 offset 변수
        private Point offset;

        // 생성자
        public DialogForm(string guName,  // 구 이름
                          string dongName,  // 동 이름
                          string minCost,  // 최소 비용
                          string maxCost,  // 최대 비용
                          string salesIncome,  // 월 추정 매출
                          string salesPeople,  // 유동 인구 수
                          string facilityCnt,  // 다중 이용 시설 수
                          string resultRate,  // 적합 등급
                          string resultCnt,  // 올리브영 매장 수
                          string resultCompete  // 경쟁 업체 수
            )
        {
            InitializeComponent();
            SetGradePictureBox(resultRate);
            regionNameLabel.Text = $"{guName} {dongName} 지역의 분석 결과는...";
            mainLabel.Text = $"월 추정매출: {salesIncome}만원 \n 유동인구: {salesPeople}명 \n 다중 이용 시설 수: {facilityCnt}개 \n 경쟁업체 수: {resultCompete}개 \n 올리브영 매장 수: {resultCnt}개";
            finalLabel.Text = $"예상 창업 비용은 \n\n {minCost} ~ {maxCost}입니다.";

        }

        // gradePictureBox에 들어갈 이미지와 폰트 색상을 등급에 따라 결정합니다.
        private void SetGradePictureBox(string resultRate)
        {
            int grade = int.Parse(resultRate[0].ToString());
            gradePictureBox.Image = Properties.Resources.ResourceManager.GetObject($"_{grade}") as Image;

            // 0등급은 블랙 ~ 5등급 레드
            Color[] gradeColors = { Color.Black,  Color.Green, Color.LightGreen, Color.Orange, Color.OrangeRed, Color.Red };
            rateLabel.ForeColor = gradeColors[grade];
            rateLabel.Text = $"창업 적합도: {resultRate}";
        }

        // 닫기 버튼 함수
        private void exitButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        // 다이얼로그 테두리 설정
        private void dialogForm_Paint(object sender, PaintEventArgs e)
        {
            ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle,
                Color.Black, 2, ButtonBorderStyle.Solid,
                Color.Black, 2, ButtonBorderStyle.Solid,
                Color.Black, 2, ButtonBorderStyle.Solid,
                Color.Black, 2, ButtonBorderStyle.Solid);
        }

        private void DialogForm_Load(object sender, EventArgs e)
        {
            dialogTitleLabel.Font = new Font(FontManager.fontFamilys[0], 20, FontStyle.Regular, GraphicsUnit.Point, ((byte)(129)));
            regionNameLabel.Font = new Font(FontManager.fontFamilys[0], 15, FontStyle.Regular, GraphicsUnit.Point, ((byte)(129)));
            rateLabel.Font = new Font(FontManager.fontFamilys[0], 15, FontStyle.Bold, GraphicsUnit.Point, ((byte)(129)));
            mainLabel.Font = new Font(FontManager.fontFamilys[0], 13, FontStyle.Regular, GraphicsUnit.Point, ((byte)(129)));
            finalLabel.Font = new Font(FontManager.fontFamilys[0], 13, FontStyle.Bold, GraphicsUnit.Point, ((byte)(129)));
        }

        private void panel1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                offset = new Point(e.X, e.Y);
            }
        }

        private void panel1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point newLocation = this.Location;
                newLocation.X += e.X - offset.X;
                newLocation.Y += e.Y - offset.Y;
                this.Location = newLocation;
            }
        }

        private void DialogForm_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                offset = new Point(e.X, e.Y);
            }
        }

        private void DialogForm_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point newLocation = this.Location;
                newLocation.X += e.X - offset.X;
                newLocation.Y += e.Y - offset.Y;
                this.Location = newLocation;
            }
        }
    }
}

 

kakaoMap.html

더보기
<!DOCTYPE html>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<style type="text/css">
    html, body {
        width: 99%;
        height: 98%;
    }

    .wrap {
        position: relative; /* 화살표(::after)의 위치를 부모 요소 기준으로 설정하기 위함 */
        background-color: rgba(255, 234, 167, 0.9); /* 연한 주황/베이지 계열의 투명 배경 */
        border: 1px solid rgb(255, 194, 0); /* 주어진 마커 색상으로 테두리 설정 */
        border-radius: 5px; /* 둥근 모서리 */
        box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2); /* 약간의 그림자 효과 */
    }

        .wrap .info .title {
            color: rgb(0, 0, 0); /* 검은색 제목 */
            background-color: rgb(255, 194, 0); /* 주어진 마커 색상으로 제목 배경 */
            padding: 3px 5px; /* 제목의 안쪽 여백 */
            border-radius: 3px 3px 0 0; /* 상단 둥근 모서리 */
            font-family: 'Nanum Gothic', sans-serif;
        }

        .wrap .info .body .desc {
            color: rgb(84, 84, 84); /* 어두운 회색 텍스트 */
            padding: 5px; /* 설명의 안쪽 여백 */
            font-family: 'Nanum Gothic', sans-serif;
        }

        .wrap::after {
            content: "";
            position: absolute;
            bottom: -10px; /* 화살표의 위치. 이 값을 조절하면 화살표 위치 변경 가능 */
            left: 50%; /* 가운데 정렬을 위함 */
            transform: translateX(-50%); /* 화살표를 완벽하게 중앙으로 위치시키기 위한 작업 */
            width: 0;
            height: 0;
            border: 1px solid rgb(255, 194, 0); /* 주어진 마커 색상으로 테두리 설정 */
            border-left: 10px solid transparent; /* 화살표의 크기와 모양 조절 */
            border-right: 10px solid transparent; /* 화살표의 크기와 모양 조절 */
            border-top: 10px solid rgba(255, 234, 167, 0.9); /* 화살표의 색상. 배경색과 동일하게 설정 */
            border-top-color: inherit; /* 배경색과 동일하게 설정, JavaScript에서 변경되면 이에 맞춰 변경됨 */
        }


    .info .close {
        position: absolute;
        top: 5px;
        right: 5px;
        color: #888;
        width: 17px;
        height: 17px;
        background: url('https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/overlay_close.png');
    }

    .info .body .desc {
        margin-top: 5px;
    }

    .ellipsis {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>
<html>
<head>
    <meta charset="utf-8">
    <title>지도 생성하기</title>
</head>
<body>
    <div id="map" style="width:100%; height:100%; "></div>
    <script type="text/javascript" src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=9e002993cfb3567fac9baa79843a3852&libraries=services"></script>
    <script>
        var mapContainer = document.getElementById('map'), mapOption = { center: new kakao.maps.LatLng(35.1595454, 126.8526012), level: 6 };
        var map = new kakao.maps.Map(mapContainer, mapOption);

        function moveTo(lat, lng) { //특정위치로 이동
            var loc = new kakao.maps.LatLng(lat, lng);
            map.setCenter(loc);
        }

        function panTo(lat, lng) { // 부드럽게 이동
            var loc = new kakao.maps.LatLng(lat, lng);
            map.panTo(loc);
        }

        function zoomIn() //줌인
        {
            var level = map.getLevel();
            map.setLevel(level - 1);
        }

        function zoomOut() //줌아웃
        {
            var level = map.getLevel();
            map.setLevel(level + 1);
        }

        // 모든 마커 삭제하는 함수
        function clearMarkers() {
            for (var i = 0; i < markers.length; i++) {
                markers[i].setMap(null);
            }
            markers = []; // 배열 비우기
        }

        // 체크박스 타입별로 마커를 관리하기 위한 객체
        var markersByType = {};
        function add_markers(type, data) {

            var imageSrc = "https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/markerStar.png"; // 마커 이미지 원본

            var infoBackgroundColor = "rgba(255, 234, 167, 0.9)"; // 기본 배경색
            var titleBackgroundColor = "rgb(255, 194, 0)"; // 기본 제목 배경색
            var arrowColor = "rgba(255, 234, 167, 0.9)"; // 화살표의 기본 색상

            var imageSize = new kakao.maps.Size(45, 45); // 마커 크기

            // 타입별로 이미지 URL을 객체에 저장
            var imageSrcByType = {
                "편의점": "https://ifh.cc/g/xosnNw.png",
                "카페": "https://ifh.cc/g/aSthzv.png",
                "은행": "https://ifh.cc/g/okSGox.png",
                "쇼핑몰": "https://ifh.cc/g/6Lnbgc.png",
                "병원": "https://ifh.cc/g/WHpbDd.png",
                "음식점": "https://ifh.cc/g/Ayv1Oq.png",
                "공영주차장": "https://ifh.cc/g/BCOpkT.png",
                "중고등학교": "https://ifh.cc/g/lnpZAF.png",
                "대학교": "https://ifh.cc/g/BXcY85.png",
                "문화시설": "https://ifh.cc/g/w6dTZP.png",
                "bus": "https://ifh.cc/g/RpaT5V.png",
                "olive_young": "https://static.oliveyoung.co.kr/pc-static-root/image/store/point_way.png"
            };

            //해당 타입의 이미지 URL 가져오기
            var imageSrc = imageSrcByType[type];


            // type이 'olive_young'일 경우 변경사항
            if (type === "olive_young") {
                imageSrc = "https://static.oliveyoung.co.kr/pc-static-root/image/store/point_way.png"; // 올리브영 마커
                infoBackgroundColor = "rgba(230, 248, 210, 0.9)"; // 연두색의 연한 톤 배경색
                titleBackgroundColor = "rgb(155, 206, 38)"; // 연두색
                arrowColor = "rgba(230, 248, 210, 0.9)"; // 연두색의 연한 톤 화살표 색상
                var imageSize = new kakao.maps.Size(24, 35); // 마커 크기
            }


            if (!markersByType[type]) {
                markersByType[type] = [];
            }

            // for문 돌려서 마커 하나씩 추가해주기
            for (var i = 0; i < data.length; i++) {

                var markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize); // 이미지 사이즈
                var addr = data[i].addr; // 주소

                var marker = new kakao.maps.Marker({
                    map: map, // 담길 지도
                    position: data[i].latlng, // 위경도
                    title: data[i].title, // 상호명
                    image: markerImage // 마커 이미지
                });

                // 마커에 담길 내용
                var content = '<div class="wrap" style="background-color:' + infoBackgroundColor + '; border-color:' + titleBackgroundColor + '">' +
                    '    <div class="info" style="background-color:' + infoBackgroundColor + '">' +
                    '        <div class="title" style="background-color:' + titleBackgroundColor + '">' +
                    data[i].title +
                    '            <div class="close" onclick="closeOverlay(\'' + type + '\', ' + i + ')" title="닫기"></div>' +
                    '        </div>' +
                    '        <div class="body">' +
                    '            <div class="desc">' +
                    '                <div class="ellipsis">' + data[i].addr + '</div>' +
                    '            </div>' +
                    '        </div>' +
                    '    </div>' +
                    '</div>';

                // 오버레이(뒤에 담기는 메세지박스)
                var overlay = new kakao.maps.CustomOverlay({
                    content: content,
                    position: marker.getPosition(),
                    yAnchor: 1.75  // 이 값을 조정하여 오버레이의 위치를 위로 올리거나 내릴 수 있음 -> 깎아야 함
                });

                // 마커와 오버레이 설정해 줌
                markersByType[type].push({ marker: marker, overlay: overlay });

                (function (type, marker, overlay) {
                    kakao.maps.event.addListener(marker, 'click', function (mouseEvent) {
                        for (var j = 0; j < markersByType[type].length; j++) {
                            markersByType[type][j].overlay.setMap(null); // 모든 오버레이 숨기기
                        }
                        overlay.setMap(map); // 클릭한 마커의 오버레이만 보이기
                    });
                })(type, marker, overlay);

                // 버스 마커 줌 크기에 따라 숨기기
                kakao.maps.event.addListener(map, 'zoom_changed', function () {
                    // 현재 줌 레벨을 가져옴
                    var zoomLevel = map.getLevel();

                    if (zoomLevel <= 4) { // 줌 레벨이 4 이하면 (확대 상태)
                        // 모든 마커 숨기기
                        for (var i = 0; i < markersByType['bus'].length; i++) {
                            markersByType['bus'][i].marker.setMap(null);
                        }
                    } else { // 줌 레벨이 4 초과면 (축소 상태)
                        // 모든 버스 마커 보이게 하기
                        for (var i = 0; i < markersByType['bus'].length; i++) {
                            markersByType['bus'][i].marker.setMap(map);
                        }
        
                        }
                    }
                });
            }
        }

        /*여기서 오류가 뜸 여기서 오류가 뜸 -- 해결*/
        //마커의 커스텀 오버레이를 닫기 위해 호출되는 함수
        function closeOverlay(type, index) {
            if (markersByType[type] && markersByType[type][index]) {
                markersByType[type][index].overlay.setMap(null);
            }
        }

        // 사용안함(hover 할때 사용하는 부분)
        function makeOverListener(map, marker, infowindow) {
            return function () {
                infowindow.open(map, marker);
            };
        }

        function makeOutListener(infowindow) {
            return function () {
                infowindow.close();
            };
        }

        // 마커 모두 지우기
        function remove_markers(type) {
            if (markersByType[type]) {
                for (var i = 0; i < markersByType[type].length; i++) {
                    markersByType[type][i].marker.setMap(null); // 변경된 부분
                    if (markersByType[type][i].overlay) { // overlay도 존재한다면
                        markersByType[type][i].overlay.setMap(null); // overlay도 지웁니다.
                    }
                }
                markersByType[type] = [];
            }
        }
    </script>
</body>
</html>

 

draw_circle_graph.py

더보기
import pandas as pd
import matplotlib.pyplot as plt
import pandas as pd
import psycopg2
import os


# 글씨체 설정
plt.rcParams['axes.unicode_minus'] =False
plt.rcParams['font.family'] ='Malgun Gothic'

# db 호출
host = '10.10.20.103'  # 데이터베이스 호스트 주소
database = 'franchise'  # 데이터베이스 이름
user = 'postgres'  # 데이터베이스 사용자 이름
password = '1234'  # 데이터베이스 비밀번호
port = 5432  # 포트번호

conn = psycopg2.connect(host=host, database=database, user=user, password=password, port=port)
cur = conn.cursor()


def create_donut_chart(dong, todo_cnt: list):
    sizes = todo_cnt
    labels = ['10대', '20대', '30대', '기타']
    colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#c2c2f0']
    colors_ = ['#0F9B58', '#0FBC74', '#53B83A', '#3EC56B', '#1AA867', '#0FAF52', '#0FAF6B', '#53AF37']
    olive_color = ['#A4CD4A', '#FFC300', '#89CFF0', '#800080', '#AA0000']

    # 비율에 따라 도넛 모양으로 그래프 그리기

    plt.pie(sizes, labels=labels, colors=olive_color, autopct='%1.1f%%', startangle=90, wedgeprops=dict(width=0.4))

    # 도넛 모양으로 그래프 그리기
    centre_circle = plt.Circle((0, 0), 0.70, fc='white')
    fig = plt.gcf()
    fig.gca().add_artist(centre_circle)

    # 모두 동일한 비율로 그리기
    plt.axis('equal')
    plt.title(f"{dong} 인구 비율")

    ### 이미지 저장
    # 현재 경로를 얻습니다.
    current_path = os.getcwd()

    # 현재 경로 내에 'graph_pic' 폴더를 만듭니다.
    graph_folder = os.path.join(current_path, 'graph_pic')

    # 해당 폴더가 없으면 생성합니다.
    if not os.path.exists(graph_folder):
        os.makedirs(graph_folder)

    # 그림을 저장할 전체 경로를 설정합니다.
    save_path = os.path.join(graph_folder, f'{dong}_인구비율.png')
    fig.savefig(save_path)
    plt.close(fig)
    plt.show()

def return_specific_data():
    query = f"SELECT * FROM public.\"TB_POPULATION\" ;"
    cur.execute(query)
    data = cur.fetchall()

    gwangju_data = dict()
    for d in data:
        if d[1] not in gwangju_data.keys():
            gwangju_data[d[1]] = []
        if d[2] not in gwangju_data[d[1]]:
            gwangju_data[d[1]].append(d[2])
    dong_list = list(gwangju_data.values())
    dong_list_ = []
    for list_ in dong_list:
        if type(list_) == list:
            dong_list_.extend(list_)
    print(dong_list_)
    for dong in dong_list_:
        return_data_by_dong(dong)

def return_data_by_dong(dong):
    query = f"SELECT * FROM public.\"TB_POPULATION\" WHERE \"POP_DONG\" = '{dong}';"
    print(query)
    cur.execute(query)
    data = cur.fetchall()
    print(data)
    data = data[0]
    id = data[0]
    gu = data[1]
    dong = data[2]
    teen = data[3] + data[4]
    twenty = data[5] + data[6]
    thirty = data[7] + data[8]
    etc_ = data[9]
    create_donut_chart(dong=dong, todo_cnt=[teen, twenty, thirty, etc_])


    conn.commit()

return_specific_data()

cur.close()
conn.close()
​

import psycopg2
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import pandas as pd
import numpy as np
import os

plt.rcParams['font.family'] = 'Malgun Gothic'
plt.rcParams['axes.unicode_minus'] = False

host = '10.10.20.103'  # 데이터베이스 호스트 주소
database = 'franchise'  # 데이터베이스 이름
user = 'postgres'  # 데이터베이스 사용자 이름
password = '1234'  # 데이터베이스 비밀번호
port = 5432  # 포트번호

conn = psycopg2.connect(host=host, database=database, user=user, password=password, port=port)
cur = conn.cursor()



class Graph:

    # 도넛 모양으로 그래프 그려주는 함수
    def create_donut_chart(self, gu, dong, todo_cnt: list):
        sizes = todo_cnt
        labels = ['10대', '20대', '30대', '기타']

        # 색 설정
        colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#c2c2f0']
        # colors_ = ['#0F9B58', '#0FBC74', '#53B83A', '#3EC56B', '#1AA867', '#0FAF52', '#0FAF6B', '#53AF37']
        olive_color = ['#A4CD4A', '#FFC300', '#89CFF0', '#800080', '#AA0000']
        pastel_color_ = ['#B2F2BB', '#FFF5B1', '#AEDFF7', '#AFEEF2', '#FFDAC1']
        # pastel_color = ['#B2E09B', '#C1E5A9', '#AEDF93', '#BCE6B4', '#C8EDC7']

        # 비율에 따라 도넛 모양으로 그래프 그리기
        plt.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90, wedgeprops=dict(width=0.4))

        # 도넛 모양으로 그래프 그리기
        centre_circle = plt.Circle((0, 0), 0.70, fc='white')
        fig = plt.gcf()
        fig.gca().add_artist(centre_circle)

        # 모두 동일한 비율로 그리기
        plt.axis('equal')

        # 제목
        plt.title(f"{gu} {dong} 인구 비율")
        plt.show()  # 그래프 띄우기

        # 그래프 저장(show와 동시에 못 사용함)
        # save_graph(gu, dong, 'sample_folder')

    # 행정동별 타겟인구수
    def TARGET_POPULATION_ADMINISTRATIVE_DONG(self):
        query = f"SELECT * FROM public.\"TB_POPULATION\" ;"
        cur.execute(query)
        data = cur.fetchall()

        gwangju_data = {'광산구': [], '남구': [], '동구': [], '북구': [], '서구': []}
        for d in data:
            if d[2] not in gwangju_data.values():
                if d[1] == '광산구':
                    gwangju_data['광산구'].append(d[2])
                if d[1] == '남구':
                    gwangju_data['남구'].append(d[2])
                if d[1] == '동구':
                    gwangju_data['동구'].append(d[2])
                if d[1] == '북구':
                    gwangju_data['북구'].append(d[2])
                if d[1] == '서구':
                    gwangju_data['서구'].append(d[2])
        print(gwangju_data)
        keys = list(gwangju_data.keys())

        for i in gwangju_data:
            for j in gwangju_data[i]:
                print(i, j)
                return_data_by_dong(i, j)

    # 행정동별 인구수 리턴값
    def return_data_by_dong(gu, dong):
        query = f"SELECT * FROM public.\"TB_POPULATION\" WHERE \"POP_GU\" = '{gu}' AND \"POP_DONG\" = '{dong}';"
        print(query)
        cur.execute(query)
        data = cur.fetchall()
        print(data)
        data = data[0]
        id = data[0]
        gu_ = data[1]
        dong_ = data[2]
        teen = data[3] + data[4]
        twenty = data[5] + data[6]
        thirty = data[7] + data[8]
        etc_ = data[9]
        print(id, gu_, dong_, teen, twenty, thirty, etc_)
        create_donut_chart(gu=gu_, dong=dong_, todo_cnt=[teen, twenty, thirty, etc_])

    # 행정동별 월평균추정매출과 경쟁업체
    def AVERAGE_MONTHLY_ESTIMATED_SALES_COMPETITORS(self):

        sql_income = "select * from \"TB_SALES\" where \"SALES_GU\" = '북구'"
        cur.execute(sql_income)
        datas = cur.fetchall()

        dong_list = list()
        income_list = list()
        compete_list = list()
        for data in datas:
            dong = data[2]
            income = data[3]
            compete = data[4]

            if income != 0:
                dong_list.append(dong)
                income_list.append(income)
                compete_list.append(compete)

        x_value = np.array(dong_list)
        y1 = np.array(income_list)
        y2 = np.array(compete_list)

        fig, ax1 = plt.subplots()

        ax1.plot(x_value, y1, '-o', color='black', markersize=5, linewidth=3, alpha=0.7, label='월평균추정매출')
        ax1.set_xlabel('행정동')
        ax1.set_ylabel('행정동별 월평균추정매출')
        ax1.tick_params(axis='both', direction='in')

        ax2 = ax1.twinx()
        ax2.bar(x_value, y2, color='deeppink', label='경쟁업체 수', alpha=0.7, width=0.7)
        ax2.set_ylabel('동일업종 경쟁업체')
        ax2.tick_params(axis='y', direction='in')

        ax1.set_zorder(ax2.get_zorder() + 10)
        ax1.patch.set_visible(False)

        ax1.legend(loc='upper left')
        ax2.legend(loc='upper right')

        plt.title("행정동별 월평균추정매출과 경쟁업체")
        plt.show()
    # 행정동별 동일업종 월평균추정매출
    def AVERAGE_MONTHLY_ESTIMATED_SALES_SAME_INDUSTRY(self):
        """행정동별 동일업종 월평균추정매출"""
        sql_dong = "select * from \"TB_SALES\" where \"SALES_GU\" = '광산구' order by \"SALES_DONG\" asc"
        cur.execute(sql_dong)
        dong_datas = cur.fetchall()

        sql_pop = "select * from \"TB_POPULATION\" where \"POP_GU\" = '광산구' order by \"POP_DONG\" asc"
        cur.execute(sql_pop)
        pop_datas = cur.fetchall()

        dong_list = list()
        income_list = list()
        for data in dong_datas:
            dong = data[2]
            income = data[3]
            if income == 0:
                pass
            else:
                dong_list.append(dong)
                income_list.append(income)

        target_list = list()
        etc_list = list()
        for data in pop_datas:
            dong = data[2]
            target_age = sum(data[3:9])
            etc_age = data[-1]
            if dong not in dong_list:
                pass
            else:
                target_list.append(target_age)
                etc_list.append(etc_age)

        x = np.array(dong_list)
        bar = np.array(income_list)
        line = np.array(target_list)

        plt.rcParams['figure.figsize'] = (4, 3)
        plt.rcParams['font.size'] = 12

        x = np.array(dong_list)
        y1 = np.array(income_list)
        y2 = np.array(target_list)

        fig, ax1 = plt.subplots()

        ax1.plot(x, y1, '-s', color='green', markersize=7, linewidth=5, alpha=0.7, label='Price')
        ax1.set_xlabel('행정동')
        ax1.set_ylabel('동일업종 월평균추정매출')
        ax1.tick_params(axis='both', direction='in')

        ax2 = ax1.twinx()
        ax2.bar(x, y2, color='deeppink', label='Demand', alpha=0.7, width=0.7)
        ax2.set_ylabel("행정동별 1030 인구")
        ax2.tick_params(axis='y', direction='in')

        plt.show()

    # 다중이용시설 그래프
    def MULTI_USE_FACILITY_GRAPH(self):
        # 색상 설정
        color_list = ['#FFD1DC', '#AEDFF7', '#DCAEFE', '#B2F2BB', '#FFAD9F', '#FFDAC1', '#FFF5B1', '#AFEEF2',
                      '#FFE5B4', '#CBA3FF', '#FFB3BA', '#BAE1FF', '#FFE0B5', '#D1A3FF', '#A8FFD9']

        # 테이블 불러오기
        qurey = "select \"GU_NAME\", \"H_DONG_NAME\" from \"TB_DONG\""
        cur.execute(qurey)
        dong_datas = cur.fetchall()

        facilty_dict = dict()
        for data in dong_datas:
            gu = data[0]
            dong = data[1]
            if not gu in facilty_dict.keys():
                facilty_dict[gu] = []
            if dong not in facilty_dict[gu]:
                facilty_dict[gu].append(dong)
        print(facilty_dict)

        gu_list = list(facilty_dict.keys())
        for gu in gu_list:
            for dong in facilty_dict[gu]:
                sql_facility = f"select \"FACILITY_TYPE\", count(\"FACILITY_DONG\") from \"TB_FACILITY\" where \"FACILITY_GU\" = '{gu}' and \"FACILITY_DONG\" = '{dong}' group by \"FACILITY_TYPE\" "
                print(sql_facility)
                cur.execute(sql_facility)
                datas = cur.fetchall()

                # 다중이용시설, 값 담는 리스트
                facility_list = list()
                count_list = list()
                # 데이터 리스트에 추가하기
                for data in datas:
                    # print(data)
                    facility_type, facility_count = data[0], data[1]
                    facility_list.append(facility_type)
                    count_list.append(facility_count)
                print(facility_list, count_list)



                x_lables = np.arange(len(facility_list))
                facility = facility_list
                cnt = count_list

                # 제목 설정
                plt.title(f"{gu} {dong} 다중이용시설")

                # 그래프 색상 적용
                plt.bar(x_lables, cnt, color=color_list[:len(facility_list)])

                # 그래프에 값 넣기
                for i in range(len(cnt)):
                    plt.text(x_lables[i], cnt[i] - 0.5, str(cnt[i]), ha='center', fontsize=10, color='black')

                # 그래프 그리기
                # y축 값 설정
                if max(cnt) > 20:
                    plt.yticks(np.arange(0, max(cnt) + 1, 5))  # 5 단위로 나눔
                else:
                    plt.yticks(np.arange(0, max(cnt) + 1, 1))  # 1 단위로 나눔
                plt.xticks(x_lables, facility)
                plt.xticks(rotation=30)  # x 스틱 회전

                # plt.show()
                # plt.yticks(np.arange(min(cnt), max(cnt) + 1, step=1))

                plt.show()
    # 행정동별 면적 범위별 평균 보증금과 임대료
    def AVERAGE_WARRANTY_AREA_RANGE(self):
        sql_gu = 'select distinct "H_DONG_NAME", "GU_NAME" from "TB_DONG"'
        cur.execute(sql_gu)
        datas = cur.fetchall()

        for data in datas:
            dong_name = data[0]
            gu_name = data[1]
            sql_deal = f"select * from \"TB_DEAL\"  where \"DEAL_DONG\" = '{dong_name}' and \"DEAL_TYPE\" = '월세'"
            cur.execute(sql_deal)
            deal_datas = cur.fetchall()

            area_list = ['105 ~ 120', "121 ~ 135", '136 ~ 150', '151 ~ 165', '166 ~ 180', '181 ~ 200']
            deposit_1 = list()
            deposit_2 = list()
            deposit_3 = list()
            deposit_4 = list()
            deposit_5 = list()
            deposit_6 = list()

            rent_1 = list()
            rent_2 = list()
            rent_3 = list()
            rent_4 = list()
            rent_5 = list()
            rent_6 = list()

            for deal_data in deal_datas:
                # dong = data[4]
                deposit = deal_data[-4]
                rent = deal_data[-3]
                area = deal_data[-1]

                if rent != None:
                    if 105 <= area < 121:
                        rent_1.append(rent)
                        deposit_1.append(deposit)
                    elif 121 <= area < 136:
                        rent_2.append(rent)
                        deposit_2.append(deposit)
                    elif 135 <= area < 151:
                        rent_3.append(rent)
                        deposit_3.append(deposit)
                    elif 150 <= area < 166:
                        rent_4.append(rent)
                        deposit_4.append(deposit)
                    elif 166 <= area < 181:
                        rent_5.append(rent)
                        deposit_5.append(deposit)
                    elif 180 <= area < 201:
                        rent_6.append(rent)
                        deposit_6.append(deposit)

            avr_deposit = [self.calculate_average(deposit_list) for deposit_list in
                           [deposit_1, deposit_2, deposit_3, deposit_4, deposit_5, deposit_6]]
            avr_rent = [self.calculate_average(rent_list) for rent_list in
                        [rent_1, rent_2, rent_3, rent_4, rent_5, rent_6]]

            avr_deposit = [0 if np.isnan(value) else value for value in avr_deposit]
            avr_rent = [0 if np.isnan(value) else value for value in avr_rent]

            fig, ax1 = plt.subplots()

            ax1.plot(area_list, avr_deposit, '-o', color='#387D32', markersize=5, linewidth=3, alpha=0.7, label='평균 보증금')
            ax1.set_xlabel('면적')
            ax1.set_ylabel('범위별 평균 보증금')

            ax2 = ax1.twinx()

            # 그래프 색상 적용
            bar_color = '#A4CD4A'
            line_color = '#387D32'  # 진한 녹색
            ax2.bar(area_list, avr_rent, color=bar_color, label='평균 임대료', alpha=0.7, width=0.7)
            # ax2.bar(area_list, avr_rent, color=color_list[:len(datas)], label='평균 임대료', alpha=0.7, width=0.7)
            for i, value in enumerate(avr_rent):
                # ax2.bar(area_list[i], value, color=color_list[i], label=area_list[i], alpha=0.7, width=0.7)
                if round(avr_rent[i]) != 0:
                    plt.text(area_list[i], avr_rent[i] - 10, str(round(avr_rent[i])), ha='center', fontsize=11,
                             color='black', fontweight='bold')

            # ax2.bar(area_list, avr_rent, color='deeppink', label='평균 임대료', alpha=0.7, width=0.7)
            ax2.set_ylabel('범위별 평균 임대료')

            ax1.set_zorder(ax2.get_zorder() + 10)
            ax1.patch.set_visible(False)

            ax1.legend(loc='upper left')
            ax2.legend(loc='lower right')

            # yticks 설정
            if max(avr_deposit) == 0:
                ax1.set_yticks(np.arange(0, 1000, step=100))
            if max(avr_rent) == 0:
                ax2.set_yticks(np.arange(0, 1000, step=100))


            plt.title(f"{gu_name} {dong_name} 면적 범위별 평균 보증금과 임대료")
            self.save_graph(gu=gu_name, dong=dong_name, folder="구동별 보증금임대료")
            plt.show()
    # 그래프 저장하는 함수
    def SAVE_GRAPH(self, gu, dong, folder):
        """

        :param gu: 구 이름
        :param dong: 동 이름
        :param folder: 저장할 폴더
        :return:
        """
        # 그래프 저장
        # 현재 경로를 얻습니다.
        current_path = os.getcwd()

        # 현재 경로 내에 'facility_pic' 폴더를 생성
        graph_folder = os.path.join(current_path, f'{folder}')

        # 해당 폴더가 없으면 생성
        if not os.path.exists(graph_folder):
            os.makedirs(graph_folder)

        # 그림을 저장할 전체 경로를 설정
        fig = plt.gcf()
        save_path = os.path.join(graph_folder, f"{gu}_{dong}")
        fig.savefig(save_path)
        plt.close(fig)

    def calculate_average(self, data_list):
        """평균값 반환하는 함수"""
        valid_data = [value for value in data_list if value is not None and not np.isnan(value)]
        return np.mean(valid_data)

    # 구별 1030 인구대비 월평균추정매출
    def MONTHLY_SALES_RELATIVE_POPULATION_DISTINCTION(self):
        gu_list = ['광산구', '북구', '남구', '동구', '서구']
        for gu_name in gu_list:
            sql_dong = f"select * from \"TB_SALES\" where \"SALES_GU\" = '{gu_name}' order by \"SALES_DONG\" asc"
            cur.execute(sql_dong)
            dong_datas = cur.fetchall()

            sql_pop = f"select * from \"TB_POPULATION\" where \"POP_GU\" = '{gu_name}' order by \"POP_DONG\" asc"
            cur.execute(sql_pop)
            pop_datas = cur.fetchall()

            dong_list = list()
            income_list = list()
            for data in dong_datas:
                dong = data[2]
                income = data[3]
                if income == 0:
                    pass
                else:
                    dong_list.append(dong)
                    income_list.append(income)

            target_list = list()
            etc_list = list()
            for data in pop_datas:
                dong = data[2]
                target_age = sum(data[3:9])
                etc_age = data[-1]
                if dong not in dong_list:
                    pass
                else:
                    target_list.append(target_age)
                    etc_list.append(etc_age)

            x = np.array(dong_list)
            bar = np.array(income_list)
            line = np.array(target_list)

            plt.rcParams['figure.figsize'] = (4, 3)
            plt.rcParams['font.size'] = 12

            x = np.array(dong_list)
            y1 = np.array(income_list)
            y2 = np.array(target_list)

            # 화면 크기 조정
            # fig, ax1 = plt.subplots()
            fig, ax1 = plt.subplots(figsize=(9, 5))

            # 색상 설정
            color_list = ['#FFD1DC', '#AEDFF7', '#DCAEFE', '#B2F2BB', '#FFAD9F', '#FFDAC1', '#FFF5B1', '#AFEEF2',
                          '#FFE5B4', '#CBA3FF', '#FFB3BA', '#BAE1FF', '#FFE0B5', '#D1A3FF', '#A8FFD9']


            ax1.plot(x, y1, '-s', color='#2E4053', markersize=7, linewidth=4, alpha=0.7, label='Price') # 검정
            # ax1.plot(x, y1, '-s', color='#387D32', markersize=7, linewidth=5, alpha=0.7, label='Price') # 녹색
            ax1.set_xlabel('행정동')
            ax1.set_ylabel('동일업종 월평균추정매출')
            ax1.tick_params(axis='both', direction='in')

            ax1.set_xticks(np.arange(len(dong_list)))  # 틱의 위치 설정
            ax1.set_xticklabels(dong_list, rotation=45)  # 회전을 적용하여 x-axis 레이블을 설정

            ax2 = ax1.twinx()
            ax2.set_ylabel('10대 ~ 30대 인구수')

            bar_color = '#A4CD4A'
            line_color = '#387D32'  # 진한 녹색

            for i, value in enumerate(y2):
                ax2.bar(x[i], y2[i], color=color_list[i % len(color_list)], alpha=0.7, width=0.7) # 파스텔
                # ax2.bar(x[i], y2[i], color='#A4CD4A', alpha=0.7, width=0.7) # 녹색

                if round(y2[i]) != 0:
                    plt.text(x[i], y2[i] + 5, str(round(y2[i])),
                             ha='center', fontsize=11, color='black',
                             fontweight='bold', zorder=3)  # y 위치를 조정하고 zorder 추가
            ax1.set_zorder(ax2.get_zorder() + 10)
            ax1.patch.set_visible(False)
            plt.xticks(rotation=45)  # x 스틱 회전
            ax2.tick_params(axis='y', direction='in')

            plt.title(f'{gu_name} 1030 인구대비 월평균추정매출')

            plt.show()
    # 행정동별 월평균추정매출
    def MONTHLY_AVERAGE_ESTIMATED_SALES_ADMINISTRATIVE_DISTRICT(self):
        gu_list = ['광산구', '북구', '남구', '서구', '동구']
        for gu_name in gu_list:
            sql_income = f"select * from \"TB_SALES\" where \"SALES_GU\" = '{gu_name}'"
            cur.execute(sql_income)
            datas = cur.fetchall()

            dong_list = list()
            income_list = list()
            compete_list = list()
            for data in datas:
                dong = data[2]
                income = data[3]
                compete = data[4]

                if income != 0:
                    dong_list.append(dong)
                    income_list.append(income)
                    compete_list.append(compete)

            x_value = np.array(dong_list)
            y1 = np.array(income_list)
            y2 = np.array(compete_list)

            # fig, ax1 = plt.subplots()
            fig, ax1 = plt.subplots(figsize=(9, 5))

            ax1.plot(x_value, y1, '-o', color='#2E4053', markersize=5, linewidth=3, alpha=0.7, label='월평균추정매출')
            ax1.set_xlabel('행정동')
            ax1.set_ylabel('행정동별 월평균추정매출')
            ax1.tick_params(axis='both', direction='in')

            ax2 = ax1.twinx()

            # 색상 설정
            color_list = ['#FFD1DC', '#AEDFF7', '#DCAEFE', '#B2F2BB', '#FFAD9F', '#FFDAC1', '#FFF5B1', '#AFEEF2',
                          '#FFE5B4', '#CBA3FF', '#FFB3BA', '#BAE1FF', '#FFE0B5', '#D1A3FF', '#A8FFD9']
            for i, value in enumerate(y2):
                label = '동일업종 경쟁업체' if i == 0 else None  # 첫 번째 바에만 라벨을 지정
                ax2.bar(x_value[i], y2[i], color=color_list[i % len(color_list)], alpha=0.7, width=0.7, label=label)


                if round(y2[i]) != 0:
                    plt.text(x_value[i], y2[i], str(round(y2[i])),
                             ha='center', fontsize=11, color='black',
                             fontweight='bold', zorder=2)  # y 위치를 조정하고 zorder 추가

            # ax2.bar(x_value, y2, color='deeppink', label='경쟁업체 수', alpha=0.7, width=0.7)
            ax2.set_ylabel('동일업종 경쟁업체')
            # ax2.tick_params(axis='y', direction='in')
            # x축 틱 위치와 레이블을 설정합니다.
            ax1.set_xticks(np.arange(len(x_value)))
            ax1.set_xticklabels(x_value, rotation=45)

            ax2.tick_params(axis='y', direction='in')
            # plt.xticks(rotation=45)  # x 스틱 회전

            ax1.set_zorder(ax2.get_zorder() + 10)
            ax1.patch.set_visible(False)

            ax1.legend(loc='upper left')
            ax2.legend(loc='upper right')

            plt.title(f"{gu_name} 월평균추정매출과 경쟁업체")

            # 그래프 저장
            # 현재 경로를 얻습니다.
            current_path = os.getcwd()

            # 현재 경로 내에 'facility_pic' 폴더를 생성
            graph_folder = os.path.join(current_path, '월평균추정매출과 경쟁업체')

            # 해당 폴더가 없으면 생성
            if not os.path.exists(graph_folder):
                os.makedirs(graph_folder)

            # 그림을 저장할 전체 경로를 설정
            fig = plt.gcf()
            save_path = os.path.join(graph_folder, f"{gu_name} 월평균추정매출과 경쟁업체")
            fig.savefig(save_path)
            plt.close(fig)
            # plt.show()

            plt.show()

    # 정규화 전 분포도 그리기
    def PRE_NORMALIZATION_GRAPH(self):

        df = pd.read_csv('정규화전.csv')

        areas = df['구동'].tolist()

        # 나머지 열은 각 카테고리에 해당하는 값
        categories = df.columns[1:].tolist()

        # 컬러맵 설정
        colormap = plt.cm.tab20
        color_list = [colormap(i) for i in range(len(categories))]
        color_list
        # colors = ['red', 'blue', 'green']

        fig, ax = plt.subplots(figsize=(16, 10))

        # 각 지역에 대한 산점도 그리기
        for idx, category in enumerate(categories):
            y_values = df[category].tolist()
            ax.scatter(areas, y_values, color=color_list[idx], label=category)

        ax.set_ylabel('값')
        ax.set_xlabel('지역명')
        ax.set_title('지역별 카테고리 값(정규화 전)')
        ax.legend()

        plt.xticks(rotation=45)
        plt.tight_layout()
        plt.show()
    # 정규화 이후 분포도 그리기
    def GRAPHS_NORMALIZATION_GRAPH(self):

        df2 = pd.read_csv('정규화후.csv')
        # df2
        areas = df2['구동'].tolist()

        # 나머지 열은 각 카테고리에 해당하는 값
        categories2 = df2.columns[1:].tolist()

        # # 컬러맵 설정
        colormap = plt.cm.tab20
        color_list = [colormap(i) for i in range(len(categories2))]
        # # colors = ['red', 'blue', 'green']

        fig, ax = plt.subplots(figsize=(16, 10))

        # 각 지역에 대한 산점도 그리기
        for idx, category in enumerate(categories2):
            y_values = df2[category].tolist()
            ax.scatter(areas, y_values, color=color_list[idx], label=category)

        ax.set_ylabel('값')
        ax.set_xlabel('지역명')
        ax.set_title('지역별 카테고리 값(정규화 후)')
        ax.legend()

        plt.xticks(rotation=45)
        plt.tight_layout()
        plt.show()


if __name__ == '__main__':
    Graph().MONTHLY_AVERAGE_ESTIMATED_SALES_ADMINISTRATIVE_DISTRICT()

프로그램 시연

프로그램 구동 사진

1. 콤보박스 선택하지 않았을때 경고창

 

2. 동 선택했을때 올리브영 마커와 부동산매매가
3. 체크박스 마커 선택과 동별 인구비율
4. 종합 분석 결과 다이얼로그
5. 등급별로 달라지는 종합분석결과 다이얼로그
6. 상세설명창_부동산매매가(매매,월세)
7. 상세설명창_인구비율
8. 상세설명창_다중이용시설
9. 상세설명창_보증금임대료
10. 상세설명창_월평균추정매출
11. 상세설명창_월평균추정매출과 경쟁업체 수
12. 상세설명창_상가 분포도

프로그램 구동 영상

올리브영_입지추천_시연.gif

 

 


첨부파일

https://github.com/guaba98/FranchiseProject

 

GitHub - guaba98/FranchiseProject: 프랜차이즈 상권 분석 프로그램

프랜차이즈 상권 분석 프로그램. Contribute to guaba98/FranchiseProject development by creating an account on GitHub.

github.com

 

profile

소연의_개발일지

@ssoyxon

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!