From 084d3ed9cadee5f4fa7e16ba5dc922f9814dd736 Mon Sep 17 00:00:00 2001 From: OkunElya Date: Fri, 10 Apr 2026 11:34:16 +1000 Subject: [PATCH] lab 5 done --- labs/lab5/src/App.jsx | 3 +- labs/lab5/src/CSS/App.css | 22 ++++ labs/lab5/src/components/Chart.jsx | 77 ++++++++---- labs/lab5/src/components/ChartDraw.jsx | 157 +++++++++++++++++++++++++ labs/lab5/src/components/TableRow.jsx | 2 +- 5 files changed, 239 insertions(+), 22 deletions(-) create mode 100644 labs/lab5/src/components/ChartDraw.jsx diff --git a/labs/lab5/src/App.jsx b/labs/lab5/src/App.jsx index ded1a2d..6587242 100644 --- a/labs/lab5/src/App.jsx +++ b/labs/lab5/src/App.jsx @@ -2,6 +2,7 @@ import { useState } from 'react' import Table from './components/Table.jsx'; import buildings from './data.js'; import './CSS/App.css' +import Chart from './components/Chart.jsx' import Task from './Task.jsx' function App() { const [count, setCount] = useState(0) @@ -9,7 +10,7 @@ function App() { return (

Самые высокие здания и сооружения

- + {/* */} diff --git a/labs/lab5/src/CSS/App.css b/labs/lab5/src/CSS/App.css index f716aeb..f6c127e 100644 --- a/labs/lab5/src/CSS/App.css +++ b/labs/lab5/src/CSS/App.css @@ -32,3 +32,25 @@ span:hover,.selected { background-color: blue; color:white; } + + + +svg { + width: 800px; + height: 400px; +} + +svg path, line { + fill: none; + stroke: black; + width: 1px; +} + +svg text { + font: 8px Verdana; +} + +.bad-selection{ + color: red; + border: solid thin red; +} \ No newline at end of file diff --git a/labs/lab5/src/components/Chart.jsx b/labs/lab5/src/components/Chart.jsx index 883a07a..19d537e 100644 --- a/labs/lab5/src/components/Chart.jsx +++ b/labs/lab5/src/components/Chart.jsx @@ -1,32 +1,69 @@ -const Chart = (props) => { +import { useState } from "react"; +import ChartDraw from './ChartDraw.jsx'; +import * as d3 from "d3"; - return ( + +const Chart = (props) => { + const [ox, setOx] = useState("Страна"); + const [oy, setOy] = useState([true, false]); + const [graphType, setGraphType] = useState(0); + + const handleSubmit = (event) => { + event.preventDefault(); + setOx(event.target["ox"].value); + setOy([event.target["oy"][0].checked, event.target["oy"][1].checked]); + setGraphType(event.target["graphType"].value); + } + + const createArrGraph = (data, key) => { + const groupObj = d3.group(data, d => d[key]); + let arrGraph = []; + for (let entry of groupObj) { + let minMax = d3.extent(entry[1].map(d => d['Высота'])); + arrGraph.push({ labelX: entry[0], values: minMax }); + } + return arrGraph; + } + + return ( <>

Визуализация

-
+

Значение по оси OX:

-
- - Страна -
- - Год -
+
+ + Страна +
+ + Год +
+

Значение по оси OY

-
- - Максимальная высота
- - Минимальная высота -
+ {(!oy[0]&& !oy[1]) && Выберите хотя бы одно} +
+ + Минимальная высота +
+ + Максимальная высота + +
+

Тип диаграммы

+
+ +
-

+

- - - ) + + + + ) } export default Chart; \ No newline at end of file diff --git a/labs/lab5/src/components/ChartDraw.jsx b/labs/lab5/src/components/ChartDraw.jsx new file mode 100644 index 0000000..c7f5b26 --- /dev/null +++ b/labs/lab5/src/components/ChartDraw.jsx @@ -0,0 +1,157 @@ +import * as d3 from "d3"; +import { useEffect, useRef, useState } from "react"; + + +function drawGraph(data, keyX, drawMin, drawMax, graphtype) { + // значения по оси ОХ + + // создаем массив для построения графика + console.log(keyX) + if(keyX=="Год"){ + data = d3.sort(data, (x,y)=>Number(x["labelX"])-Number(y["labelX"])); + } + const svg = d3.select("svg") + svg.selectAll('*').remove(); + + // создаем словарь с атрибутами области вывода графика + const attr_area = { + width: parseFloat(svg.style('width')), + height: parseFloat(svg.style('height')), + marginX: 50, + marginY: 50 + } + + // создаем шкалы преобразования и выводим оси + const [scX, scY] = createAxis(svg, data, attr_area,[drawMin,drawMax]); + + // рисуем график + + const scaleYDomain = d3.extent(data.map(d => d.values[1])); + if (drawMin && drawMax){ + createChart(svg, data, scX, scY, attr_area, "blue", 0,graphtype,0,scaleYDomain) + createChart(svg, data, scX, scY, attr_area, "red", 1,graphtype,0,scaleYDomain) + } + else if (drawMin) { + createChart(svg, data, scX, scY, attr_area, "blue", 0,graphtype,1,scaleYDomain) + } + else if (drawMax) { + createChart(svg, data, scX, scY, attr_area, "red", 1,graphtype,1,scaleYDomain) + } +} + +function createAxis(svg, data, attr_area, selections) { + // находим интервал значений, которые нужно отложить по оси OY + // максимальное и минимальное значение и максимальных высот по каждой стране + const max = d3.max(data,d => d.values[Number(selections[1])]); + const min = d3.min(data,d => d.values[Number(selections[0])]); + // console.log(max,min,data) + // функция интерполяции значений на оси + // по оси ОХ текстовые значения + const scaleX = d3.scaleBand() + .domain(data.map(d => d.labelX)) + .range([0, attr_area.width - 2 * attr_area.marginX]); + + const scaleY = d3.scaleLinear() + .domain([min * 0.85, max * 1.1]) + .range([attr_area.height - 2 * attr_area.marginY, 0]); + + // создание осей + const axisX = d3.axisBottom(scaleX); // горизонтальная + const axisY = d3.axisLeft(scaleY); // вертикальная + + // отрисовка осей в SVG-элементе + svg.append("g") + .attr("transform", `translate(${attr_area.marginX}, + ${attr_area.height - attr_area.marginY})`) + .call(axisX) + .selectAll("text") // подписи на оси - наклонные + .style("text-anchor", "end") + .attr("dx", "-.8em") + .attr("dy", ".15em") + .attr("transform", d => "rotate(-45)"); + + svg.append("g") + .attr("transform", `translate(${attr_area.marginX}, ${attr_area.marginY})`) + .call(axisY); + + return [scaleX, scaleY] +} + +function createChart(svg, data, scaleX, scaleY, attr_area, color, valueIdx,isHistogram,horisontalScale,scaleYDomain) { + if (isHistogram){ + svg.selectAll(".dot") + .data(data) + .enter() + .append("rect") + .attr("x", d => scaleX(d.labelX)+valueIdx*6) + .attr("y", d => scaleY(d.values[valueIdx])) + .attr("width",6*(horisontalScale+1)) + .attr("height",d => scaleY(scaleYDomain[0]*0.85)-scaleY(d.values[valueIdx])) + .attr("transform", `translate(${attr_area.marginX}, ${attr_area.marginY})`) + .style("fill", color) + + + } + else{ + const r = 4; + + svg.selectAll(".dot") + .data(data) + .enter() + .append("circle") + .attr("r", r) + .attr("cx", d => scaleX(d.labelX) + scaleX.bandwidth() / 2+valueIdx*4) + .attr("cy", d => scaleY(d.values[valueIdx])) + .attr("transform", `translate(${attr_area.marginX}, ${attr_area.marginY})`) + .style("fill", color) +} +} + + +const ChartDraw = (props) => { + const chartRef = useRef(null); + + const [width, setWidth] = useState(0); + const [height, setHeight] = useState(0); + + // заносим в состояния ширину и высоту svg-элемента + useEffect(() => { + const svg = d3.select(chartRef.current); + setWidth(parseFloat(svg.style('width'))); + setHeight(parseFloat(svg.style('height'))); + }); + // задаем отступы в svg-элементе + const margin = { + top:10, + bottom:60, + left:40, + right:10 + }; + + // вычисляем ширину и высоту области для вывода графиков + const boundsWidth = width - margin.left - margin.right; + const boundsHeight = height - margin.top - margin.bottom; + + useEffect(() => { + if(boundsWidth<0||boundsHeight<0){ + return; + } + const svg = d3.select(chartRef.current); + // выводим прямоугольник, + svg + .append("rect") + .attr("x", margin.left) + .attr("y", margin.top) + .attr("width", boundsWidth) + .attr("height", boundsHeight) + .style("fill", "lightgrey"); + console.log(props) + drawGraph(props.data,props.ox,props.minMax[0],props.minMax[1],Number(props.graphType)) + }); + + return ( + + ) +} + +export default ChartDraw; \ No newline at end of file diff --git a/labs/lab5/src/components/TableRow.jsx b/labs/lab5/src/components/TableRow.jsx index 2446adf..567a60c 100644 --- a/labs/lab5/src/components/TableRow.jsx +++ b/labs/lab5/src/components/TableRow.jsx @@ -12,7 +12,7 @@ const TableRow = (props) => { : props.row.map((item, index) =>
); return( - <> {cells} + <>{cells} ) }
{item}