diff --git a/public/scripts/chart_loader.js b/public/scripts/chart_loader.js index 1252ef7..6b3d24e 100644 --- a/public/scripts/chart_loader.js +++ b/public/scripts/chart_loader.js @@ -358,7 +358,6 @@ class ChartLoader { */ async addInteractivity() { setTimeout(() => { - console.time("2") // Set dimensions of effect canvas this.effectCanvas.width = this.canvas.width this.effectCanvas.height = this.canvas.height @@ -367,7 +366,6 @@ class ChartLoader { this.detectionCanvas.height = this.canvas.height // Draw detection map on the detection canvas this.chart.drawDetectionMap(this.detectionCanvas.getContext("2d")) - console.timeEnd("2") }, 0) } @@ -377,7 +375,6 @@ class ChartLoader { * @param {Array} data - The data to visualize on the chart. */ drawChart(chartSettings, data) { - console.time("1") let zoomManager = new ZoomManager(chartSettings.horizontalZoom, chartSettings.verticalZoom) // Choose the correct type of chart @@ -419,8 +416,6 @@ class ChartLoader { this.chart.draw() - console.timeEnd("1") - this.addInteractivity() this.addListeners(this.chart) } diff --git a/public/scripts/charts/area_chart.js b/public/scripts/charts/area_chart.js index a1ce38a..0b435a4 100644 --- a/public/scripts/charts/area_chart.js +++ b/public/scripts/charts/area_chart.js @@ -17,47 +17,56 @@ class AreaChart extends PointChart { /** * Draws the area chart on the canvas. + * @param {Boolean} async - Says if the chart should be drawn synchronously or asynchronously */ - draw() { + draw(async = true) { // Clear the canvas and draw the axis this.clear() this.drawAxis() - // Iterate over each category in the data - this.data.forEach(categ => { - // Begin a new path for drawing lines - this.ctx.beginPath() - this.ctx.lineJoin = "round" - this.ctx.strokeStyle = categ.color - - let xmax = 0 // rightmost point (some lines can be shorter) - // Iterate over each value in the category - for (let i = 0; i < this.dataLen; i++) { - // Skip empty points - if (categ.values[i] === null) continue - - // Calculate the x and y coordinates for the current value - let pos = this.getPointPos(i, categ.values[i]) - xmax = pos.x - - this.ctx.lineTo(pos.x, pos.y) - } - this.ctx.stroke() - - // Complete the area by drawing lines to the x-axis and back to the starting point - this.ctx.lineTo(xmax, this.zoomBounds.xAxis) - this.ctx.lineTo(this.zoomBounds.left, this.zoomBounds.xAxis) - this.ctx.globalAlpha = 0.5 - this.ctx.fillStyle = categ.color - this.ctx.closePath() - this.ctx.fill() - - // Reset global alpha for future drawings - this.ctx.globalAlpha = 1 - }) - - // Draw points on the chart if required - if (this.settings.displayPoints) - this.data.forEach((categ, colId) => {this.drawPoints(categ.values, colId, categ.color)}) + this.ctx.lineWidth = this.settings.lineWidth + + // Define function for async + let fn = () => { + // Iterate over each category in the data + this.data.forEach(categ => { + // Begin a new path for drawing lines + this.ctx.beginPath() + this.ctx.lineJoin = "round" + this.ctx.strokeStyle = categ.color + + let xmax = 0 // rightmost point (some lines can be shorter) + // Iterate over each value in the category + for (let i = 0; i < this.dataLen; i++) { + // Skip empty points + if (categ.values[i] === null) continue + + // Calculate the x and y coordinates for the current value + let pos = this.getPointPos(i, categ.values[i]) + xmax = pos.x + + this.ctx.lineTo(pos.x, pos.y) + } + this.ctx.stroke() + + // Complete the area by drawing lines to the x-axis and back to the starting point + this.ctx.lineTo(xmax, this.zoomBounds.xAxis) + this.ctx.lineTo(this.zoomBounds.left, this.zoomBounds.xAxis) + this.ctx.globalAlpha = 0.5 + this.ctx.fillStyle = categ.color + this.ctx.closePath() + this.ctx.fill() + + // Reset global alpha for future drawings + this.ctx.globalAlpha = 1 + }) + + // Draw points on the chart if required + if (this.settings.displayPoints) + this.data.forEach((categ, colId) => { this.drawPoints(categ.values, colId, categ.color) }) + } + + // Draw the chart + async ? setTimeout(() => fn(), 0) : fn() } } diff --git a/public/scripts/charts/line_chart.js b/public/scripts/charts/line_chart.js index 232f7a7..0f70676 100644 --- a/public/scripts/charts/line_chart.js +++ b/public/scripts/charts/line_chart.js @@ -17,34 +17,43 @@ class LineChart extends PointChart { /** * Draws the line chart on the canvas. + * @param {Boolean} async - Says if the chart should be drawn synchronously or asynchronously */ - draw() { + draw(async = true) { // Clear the canvas and draw the axis this.clear() this.drawAxis() - // Iterate over each category in the data - this.data.forEach(categ => { - // Begin a new path for drawing lines - this.ctx.beginPath() - this.ctx.lineJoin = "round" - this.ctx.strokeStyle = categ.color - - // Iterate over each value in the category - for (let i = 0; i < this.dataLen; i++) { - if (categ.values[i] === null) continue - - // Calculate the x and y coordinates for the current value - let pos = this.getPointPos(i, categ.values[i]) - - this.ctx.lineTo(pos.x, pos.y) - } - this.ctx.stroke() - this.ctx.closePath() - }) - - // Draw points on the chart if required - if (this.settings.displayPoints) - this.data.forEach((categ, colId) => {this.drawPoints(categ.values, colId, categ.color)}) + this.ctx.lineWidth = this.settings.lineWidth + + // Define function for async + let fn = () => { + // Iterate over each category in the data + this.data.forEach(categ => { + // Begin a new path for drawing lines + this.ctx.beginPath() + this.ctx.lineJoin = "round" + this.ctx.strokeStyle = categ.color + + // Iterate over each value in the category + for (let i = 0; i < this.dataLen; i++) { + if (categ.values[i] === null) continue + + // Calculate the x and y coordinates for the current value + let pos = this.getPointPos(i, categ.values[i]) + + this.ctx.lineTo(pos.x, pos.y) + } + this.ctx.stroke() + this.ctx.closePath() + }) + + // Draw points on the chart if required + if (this.settings.displayPoints) + this.data.forEach((categ, colId) => { this.drawPoints(categ.values, colId, categ.color) }) + } + + // Draw the chart + async ? setTimeout(() => fn(), 0) : fn() } } diff --git a/public/scripts/charts/smooth_area_chart.js b/public/scripts/charts/smooth_area_chart.js index 061bd23..962a96c 100644 --- a/public/scripts/charts/smooth_area_chart.js +++ b/public/scripts/charts/smooth_area_chart.js @@ -17,61 +17,72 @@ class SmoothAreaChart extends PointChart { /** * Draws the smooth area chart on the canvas. + * @param {Boolean} async - Says if the chart should be drawn synchronously or asynchronously */ - draw() { + draw(async = true) { // Clear the canvas and draw the axis this.clear() this.drawAxis() - // Iterate over each category in the data - this.data.forEach(categ => { - // Begin a new path for drawing lines - this.ctx.beginPath() - this.ctx.lineJoin = "round" - this.ctx.strokeStyle = categ.color - - // Calculate the starting point for the line - let pos1 = this.getPointPos(0, categ.values[0]) - this.ctx.moveTo(pos1.x, pos1.y) - - let xmax = 0 // rightmost point (some lines can be shorter) - - // Draw the Bezier curve - for (let i = 0; i < this.dataLen - 1; i++) { - // Calculate left point coordinates - - let leftPos = this.getPointPos(i, categ.values[i]) - - // Calculate right point coordinates - let rightPos = this.getPointPos(i + 1, categ.values[i + 1]) - - // Find middle point - let xm = (leftPos.x + rightPos.x) / 2 - // Find quarter points - let xl = (leftPos.x + xm) / 2 - let xr = (rightPos.x + xm) / 2 - - // Draw a curve that smoothly connects the points - this.ctx.bezierCurveTo(xl, leftPos.y, xr, rightPos.y, rightPos.x, rightPos.y); - xmax = rightPos.x - } - - // Complete the area by connecting the last point to the x-axis - this.ctx.lineTo(xmax, this.zoomBounds.xAxis) - this.ctx.lineTo(this.zoomBounds.left, this.zoomBounds.xAxis) - - // Set transparency and fill the area - this.ctx.globalAlpha = 0.5 - this.ctx.fillStyle = categ.color - this.ctx.closePath() - this.ctx.fill() - - // Reset transparency - this.ctx.globalAlpha = 1 - }) - - // Draw points on the chart if required - if (this.settings.displayPoints) - this.data.forEach((categ, colId) => {this.drawPoints(categ.values, colId, categ.color)}) + this.ctx.lineWidth = this.settings.lineWidth + + + // Define function for async + let fn = () => { + // Iterate over each category in the data + this.data.forEach(categ => { + // Begin a new path for drawing lines + this.ctx.beginPath() + this.ctx.lineJoin = "round" + this.ctx.strokeStyle = categ.color + + // Calculate the starting point for the line + let pos1 = this.getPointPos(0, categ.values[0]) + this.ctx.moveTo(pos1.x, pos1.y) + + let xmax = 0 // rightmost point (some lines can be shorter) + + // Draw the Bezier curve + for (let i = 0; i < this.dataLen - 1; i++) { + // Calculate left point coordinates + + let leftPos = this.getPointPos(i, categ.values[i]) + + // Calculate right point coordinates + let rightPos = this.getPointPos(i + 1, categ.values[i + 1]) + + // Find middle point + let xm = (leftPos.x + rightPos.x) / 2 + // Find quarter points + let xl = (leftPos.x + xm) / 2 + let xr = (rightPos.x + xm) / 2 + + // Draw a curve that smoothly connects the points + this.ctx.bezierCurveTo(xl, leftPos.y, xr, rightPos.y, rightPos.x, rightPos.y); + xmax = rightPos.x + } + + // Complete the area by connecting the last point to the x-axis + this.ctx.lineTo(xmax, this.zoomBounds.xAxis) + this.ctx.lineTo(this.zoomBounds.left, this.zoomBounds.xAxis) + + // Set transparency and fill the area + this.ctx.globalAlpha = 0.5 + this.ctx.fillStyle = categ.color + this.ctx.closePath() + this.ctx.fill() + this.ctx.stroke() + + // Reset transparency + this.ctx.globalAlpha = 1 + }) + + // Draw points on the chart if required + if (this.settings.displayPoints) + this.data.forEach((categ, colId) => { this.drawPoints(categ.values, colId, categ.color) }) + } + + // Draw the chart + async ? setTimeout(() => fn(), 0) : fn() } } diff --git a/public/scripts/charts/smooth_line_chart.js b/public/scripts/charts/smooth_line_chart.js index 811e3d9..0b952f1 100644 --- a/public/scripts/charts/smooth_line_chart.js +++ b/public/scripts/charts/smooth_line_chart.js @@ -64,7 +64,6 @@ class SmoothLineChart extends PointChart { } // Draw the chart - if (this.settings.displayPoints) - async ? setTimeout(() => fn(), 0) : fn() + async ? setTimeout(() => fn(), 0) : fn() } } diff --git a/public/scripts/table.js b/public/scripts/table.js index 497413d..9aafb3d 100644 --- a/public/scripts/table.js +++ b/public/scripts/table.js @@ -13,6 +13,9 @@ class Table { this.tableHead = tableElement.querySelector("thead") this.rcMenu = rcMenu + this.importElement = document.getElementById("import") + this.exportElement = document.getElementById("export") + // Initialize right-click menu options this.rcAddRow = rcMenu.querySelector("#rcAddRow") this.rcDelRow = rcMenu.querySelector("#rcDelRow") @@ -52,7 +55,89 @@ class Table { this.rcAddCol.addEventListener("mousedown", (e) => { this.addCol() }) this.rcDelCol.addEventListener("mousedown", (e) => { this.delCol() }) - this.get + this.importElement.addEventListener("input", (event) => { + let file = this.importElement.files[0] + let reader = new FileReader() + reader.onload = (e) => { + let result = e.target.result + + let rows = result.split("\n") + let header = rows[0].split(",") + rows.shift() + + this.selectedCell = this.tableBody.querySelector("#chart_table_0_values_0") + + while (this.tableBody.rows.length > 1) + this.delRow() + + while (this.tableHead.lastElementChild.childElementCount > header.length) + this.delCol() + + while (this.tableHead.lastElementChild.childElementCount < header.length) + this.addCol() + + for (let i = 0; i < header.length; i++) { + this.tableHead.querySelector("#chart_table_" + i +"_col_name").value = header[i] + } + + for (let i = 1; i < rows.length - 1; i++) + this.addRow() + for (let i = 0; i < rows.length - 1; i++) { + let cells = rows[i].split(",") + for (let j = 0; j < header.length; j++) { + console.log("#chart_table_" + j + "_values_" + i, +cells[j]) + this.tableBody.querySelector("#chart_table_" + j + "_values_" + i).value = +cells[j] + } + } + } + reader.readAsText(file) + }) + + this.exportElement.addEventListener("input", (e) => { + let newName = "" + let fileURL = "" + + let choice = this.exportElement.value + if (choice === "png") { + let iframe = document.getElementById("chartDiv") + let canvas = iframe.contentWindow.document.getElementById("chartCanvas") + fileURL = canvas.toDataURL("image/png") + newName = 'chart.png' + } + else if (choice === "csv" || choice === "txt") { + // Variable to store the final csv data + let csv_data = [] + let rows = document.getElementsByTagName('tr'); + for (let i = 0; i < rows.length; i++) { + + // Get each column data + let cols = rows[i].querySelectorAll('td,th'); + + // Stores each csv row data + let csvrow = []; + for (let j = 0; j < cols.length; j++) { + + // Get the text data of each cell of + // a row and push it to csvrow + csvrow.push(cols[j].querySelector("input").value); + } + + // Combine each column value with comma + csv_data.push(csvrow.join(",")); + } + // Combine each row data with new line character + csv_data = csv_data.join('\n'); + + let CSVFile = new Blob([csv_data], { type: "text/csv" }) + fileURL = window.URL.createObjectURL(CSVFile) + newName = 'data.' + choice + } + + let downloadLink = document.createElement("a") + downloadLink.download = newName + downloadLink.href = fileURL + downloadLink.click() + }) } handleContextMenu(rcMenu, tableElement, pos) { diff --git a/public/styles/edit_chart.css b/public/styles/edit_chart.css index 00c6d72..a1b9808 100755 --- a/public/styles/edit_chart.css +++ b/public/styles/edit_chart.css @@ -96,6 +96,10 @@ div[id^="chart_metadata_group"] { padding: 0.5rem; } +#fileDiv label { + margin-top: 1rem; +} + /* Styles for secondary div */ #secondaryDiv { flex-basis: 40%; diff --git a/public/styles/web_style.css b/public/styles/web_style.css index 7c1c84d..01c8968 100755 --- a/public/styles/web_style.css +++ b/public/styles/web_style.css @@ -167,23 +167,24 @@ h2 { color: darkred; } -#myCharts { +#myCharts, #indexDiv { height: 100%; width: 100%; margin: 1rem; text-align: center; background-color: var(--main); display: block; + overflow: auto; } -#myCharts #createChart { +#createChart { color: var(--dark); background-color: green; padding: 1rem; transition: 300ms; } -#myCharts #createChart:hover { +#createChart:hover { background-color: darkgreen; } @@ -231,6 +232,25 @@ h2 { transition: 300ms; } +#indexDiv { + display: block; + overflow: auto; +} + +#indexDiv section{ + margin-top: 4rem; +} + +#indexDiv iframe { + width: 60%; + height: 22rem; +} + +#indexDiv li { + list-style-type: none; + margin: 0.5rem; +} + /* Media query for smaller screens */ @media (max-width: 600px) { header { diff --git a/src/Document/Chart.php b/src/Document/Chart.php index 77d7ae9..58930b9 100644 --- a/src/Document/Chart.php +++ b/src/Document/Chart.php @@ -39,13 +39,14 @@ class Chart "yStep" => 1, "displaySupportLines" => true, "displayAxisValues" => true, - "pointSize" => 15, + "pointSize" => 7, "titleFont" => ["font" => 'Courier New', "size" => 25], "labelFont" => ["font" => 'Georgia', "size" => 15], "legendFont" => ["font" => 'Arial', "size" => 10], "pointBorderSize" => 2, "pointBorderColor" => "#241f31", - "horizontalZoom" => true + "horizontalZoom" => true, + "lineWidth" => 3 ]; #[MongoDB\Field(type: 'hash')] diff --git a/src/Form/Type/MetadataType.php b/src/Form/Type/MetadataType.php index b66943b..2da3355 100644 --- a/src/Form/Type/MetadataType.php +++ b/src/Form/Type/MetadataType.php @@ -154,9 +154,21 @@ class MetadataType extends AbstractType 'required' => false, ]) ) - // Zoom settings + // Line ->add( $builder->create('group5', FormType::class, [ + 'inherit_data' => true, + 'label' => 'Nastavení čar', + 'label_attr' => ['class' => 'submenuLabel arrow-down'] + ]) + ->add('lineWidth', NumberType::class, [ + 'label' => 'Tloušťka čáry', + 'required' => false, + ]) + ) + // Zoom settings + ->add( + $builder->create('group6', FormType::class, [ 'inherit_data' => true, 'label' => 'Nastavení přiblížení', 'label_attr' => ['class' => 'submenuLabel arrow-down'] @@ -172,7 +184,7 @@ class MetadataType extends AbstractType ) // Background settings ->add( - $builder->create('group6', FormType::class, [ + $builder->create('group7', FormType::class, [ 'inherit_data' => true, 'label' => 'Nastavení pozadí', 'label_attr' => ['class' => 'submenuLabel arrow-down'] diff --git a/templates/chart/edit.html.twig b/templates/chart/edit.html.twig index 956f3ae..f675653 100644 --- a/templates/chart/edit.html.twig +++ b/templates/chart/edit.html.twig @@ -43,14 +43,14 @@ let table = new Table(tableElement, rcMenu)
+

Import - Export

diff --git a/templates/docs.html.twig b/templates/docs.html.twig index 0e3d6a8..f7bb8e7 100644 --- a/templates/docs.html.twig +++ b/templates/docs.html.twig @@ -1,16 +1,86 @@ {% extends 'base.html.twig' %} {% block title %} - Dokumentace + Dokumentace {% endblock %} {% block body %} {{ parent() }} -

Interaktivní grafy

-
-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusamus odio neque est voluptatum - repellat animi dicta aliquam ipsa culpa commodi. -

-
+
+
+

Dokumentace aplikace

+

V této dokumentaci se nacházejí ukázky grafů, které je možné pomocí této aplikace vyrobit. Lze je exportovat ve statické formě, nebo je vkládat na webové stránky pomocí:

+

+ < iframe src="example.com/charts/6645cec13703bac5a30e6a41"> </iframe > + +

+
+ +

+ Bodové grafy jsou základním nástrojem pro vizualizaci datových bodů na rovině. Každý bod v grafu reprezentuje jednu konkrétní hodnotu nebo pozorování. Tyto datové body mohou být v závislosti na datech rovnoměrně nebo nerovnoměrně rozloženy napříč vodorovnou osou. Tento typ grafu je ideální pro zobrazování vztahů mezi dvěma proměnnými a umožňuje identifikovat vzory, korelace a odlehlé hodnoty. Někdy se jim proto říká korelační diagramy. V bodových grafech jsou jednotlivé body umístěny na kartézské soustavě souřadnic. Mohou být v některých případech i trojrozměrné a každý bod tedy popisují tři souřadnice, ale v praxi se tato verze příliš nepoužívá kvůli špatné přehlednosti. Bodové grafy jsou často využívány k analýze dat ve vědeckém výzkumu, ekonomii, sociologii a dalších disciplínách, kde je potřeba vizualizovat vztahy mezi proměnnými. +

+
+ +
+ +

+ Spojnicové grafy se používají k vizualizaci vztahů mezi body v čase nebo v závislosti na jiné proměnné. Tento typ grafu je vhodný pro zobrazování trendů, vývoje a dynamiky dat. Ve spojnicových grafech jsou body spojeny čarami, které zobrazují vztah mezi nimi. Tyto čáry obvykle představují vývoj dat v čase nebo vztah mezi dvěma proměnnými. Na rozdíl od bodového grafu jsou data téměř vždy rovnoměrně rozprostřena po celé délce vodorovné osy. Spojnicové grafy jsou často využívány k vizualizaci dat jako je vývoj cen, teplot nebo akcií. +

+
+ +
+ +
+ +
+ +

+ Plošné grafy vizualizují data jako plné oblasti, kde každá oblast reprezentuje jednu datovou kategorii. Tyto grafy jsou ideální pro srovnávání hodnot mezi různými kategoriemi a pro zjištění jejich relativního podílu. Jednou z hlavních výhod plošných grafů jednoduchost interpretace, protože plné plochy poskytují přehledný obraz o datech a umožňují snadno vizuálně porovnávat velikosti jednotlivých kategorií. Plošné grafy mohou nést i některé nevýhody. Mezi ně patří možná ztráta podrobností, pokud je potřeba zobrazit detailnější informace nebo individuální hodnoty. Mohou být často viděny v oblastech jako marketing, ekonomie nebo třeba ve zdravotnictví, pro srovnání prevalence různých onemocnění. +

+
+ +
+ +

+ Koláčové grafy jsou kruhové diagramy, které vizualizují data jako procentuální části celku. Každý segment v grafu odpovídá určitému procentu celkového množství. Koláčové grafy jsou často využívány pro prezentaci procentuálního rozložení různých položek, jako jsou například náklady, zisky nebo populace. Jsou také vhodné pro zobrazení struktury portfolia, podílu tržního segmentu a demografických trendů. Mohou však být méně efektivní při zobrazení velkého počtu kategorií a mohou být matoucí, pokud jsou některé segmenty příliš malé na to, aby byly přehledné. +

+
+ +
+ +
+ +
+ +

+ Sloupcové grafy jsou vynikajícím nástrojem pro vizualizaci dat, které mají jasně definované kategorie a hodnoty. Data reprezentují pomocí svislých sloupců, kde výška sloupce odpovídá hodnotě datového bodu. Jsou ideální pro srovnávání hodnot mezi různými kategoriemi a rychlé pochopení relativních hodnot. Tento typ grafu je často využíván v různých oblastech, jako jsou ekonomie, marketing, nebo vědecký výzkum, kde je potřeba srovnávat hodnoty mezi různými skupinami nebo kategoriemi dat. Díky své jednoduché interpretaci a přehlednému zobrazení je sloupcový graf oblíbeným nástrojem nejen pro analyzování dat, ale i pro prezentaci výsledků a komunikaci významných trendů nebo rozdílů. +

+
+ +
+ +

+ Skládané grafy představují pokročilejší formu sloupcových grafů, kde každý sloupec zobrazuje kumulativní hodnoty všech kategorií namísto jednotlivých hodnot. Skládané grafy jsou užitečné pro porovnání celkové velikosti a struktury různých skupin dat a používají se proto při vizualizaci trendů, jako jsou například příjmy, náklady nebo produkční výstupy. +

+
+ +

API

+
+

API pro správu grafů poskytuje několik koncových bodů pro získání seznamu grafů, zobrazení detailů konkrétního grafu, vkládání nových grafů, aktualizaci stávajících grafů a mazání grafů.

+ +
    +
  • GET /api/charts: Vrací seznam ID grafů, jež patří přihlášenému uživateli. Odpověď obsahuje seznam ID grafů a HTTP kód 200 OK.
  • + +
  • GET /api/charts/{id}: Endpoint vrací detailní informace o grafu na základě jeho ID. Odpověď obsahuje detailní informace o grafu a kód 200.
  • + +
  • POST /api/charts/insert: Umožňuje uživateli vytvořit nový graf. Data grafu se posílají v těle zprávy ve formátu JSON. Backend následně vytvoří novou entitu a přiřadí jí unikátní ID. Odpověď obsahuje název vytvořeného grafu s HTTP kódem 200. Uživatel musí být přihlášen, jinak se vrátí chyba přístupu s HTTP kódem 403.
  • + +
  • POST /api/charts/{id}/update: Umožňuje uživateli aktualizovat jeho grafy. Data se posílají v těle zprávy jako JSON. Obsahem odpovědi je název aktualizovaného grafu s HTTP kódem 200. Pokud graf patří jinému uživateli, vrátí se chyba přístupu s HTTP kódem 403.
  • + +
  • DELETE /api/charts/{id}/remove: Umožňuje smazat existující graf. Odpověď obsahuje název smazaného grafu s HTTP kódem 200. Uživatel může mazat pouze své vlastní grafy, jinak se vrátí chyba přístupu s kódem 403
  • + +
+
+
+
{% endblock %} diff --git a/templates/index.html.twig b/templates/index.html.twig index 626f709..31304ff 100644 --- a/templates/index.html.twig +++ b/templates/index.html.twig @@ -6,13 +6,21 @@ {% block body %} {{ parent() }} -
-

Interaktivní grafy

-
-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusamus odio neque est voluptatum - repellat animi dicta aliquam ipsa culpa commodi. -

-
-
+
+
+

Interaktivní grafy

+
+ +

+ Vítejte v aplikaci pro tvorbu interaktivních grafů. Klikněte na tlačítko níže a vytvořte si svůj graf. +

+ + {% if app.user %} + Vytvořit nový graf! + {% else %} + Začít! + {% endif %} +
+
+
{% endblock %}