Working Zoom

main
František Špaček 2 years ago
parent 2b3b9e3e3e
commit 6492022ead

@ -77,29 +77,29 @@ class ChartLoader {
}
getEffectObjects() {
}
addListener(newChart) {
addListener(chart) {
//Click
document.addEventListener('mousemove', (e) => {
const pos = {
x: e.clientX,
y: e.clientY
}
let shapeIndex = this.selectedShapeIndex
if (shapeIndex === null)
shapeIndex = this.getShapeIndex(this.detectionCanvas, pos)
if (shapeIndex !== null) {
let obj = newChart.objects[shapeIndex]
let obj = chart.objects[shapeIndex]
// Effect
let effectCtx = this.effectCanvas.getContext("2d")
effectCtx.clearRect(0, 0, effectCanvas.width, effectCanvas.height)
this.effectCanvas.style.opacity = 1
newChart.drawEffect(effectCtx, [obj])
chart.drawEffect(effectCtx, [obj])
this.dataDiv.style.display = "block"
@ -115,7 +115,7 @@ class ChartLoader {
this.dataDiv.style.top = pos.y - this.dataDiv.clientHeight + "px"
this.dataDiv.style.display = "block"
let name = newChart.data[obj.colId.toString()].col_name
let name = chart.data[obj.colId.toString()].col_name
this.dataDiv.innerHTML = "<b>" + name + "</b><br><p>" + obj.value + "</p>"
}
else {
@ -145,18 +145,30 @@ class ChartLoader {
else
this.selectedShapeIndex = this.clickedShapeIndex
console.log(this.clickedShapeIndex + " " +
this.getShapeIndex(this.detectionCanvas, pos) + " " +
this.shapeSelected)
/*console.log(this.clickedShapeIndex + " " +
this.getShapeIndex(this.detectionCanvas, pos) + " " +
this.shapeSelected)*/
})
window.addEventListener("resize", e => {
//newChart.updateLegend(graphSettings.displayLegend, data)
newChart.resizeCanvas(this.parent, this.legend.offsetHeight)
newChart.draw()
//chart.updateLegend(chartSettings.displayLegend, data)
chart.resizeCanvas(this.parent, this.legend.offsetHeight)
chart.draw()
this.addInteractivity()
})
if (chart.settings.horizontalZoom || chart.settings.verticalZoom)
window.addEventListener("wheel", e => {
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
chart.zoom.recalculate(e, true, true)
chart.updateBounds()
chart.draw()
this.addInteractivity()
}, { passive: false })
}
async addInteractivity() {
@ -171,40 +183,41 @@ class ChartLoader {
}, 0)
}
drawChart(graphSettings, data) {
drawChart(chartSettings, data) {
let zoomManager = new ZoomManager(chartSettings.horizontalZoom, chartSettings.verticalZoom)
//Choose the correct graph
switch (graphSettings.type) {
switch (chartSettings.type) {
case "point":
this.chart = new PointChart(this.canvas, data, graphSettings)
this.chart = new PointChart(this.canvas, data, chartSettings, zoomManager)
break
case "line":
this.chart = new LineChart(this.canvas, data, graphSettings)
this.chart = new LineChart(this.canvas, data, chartSettings, zoomManager)
break
case "smoothline":
this.chart = new SmoothLineChart(this.canvas, data, graphSettings)
this.chart = new SmoothLineChart(this.canvas, data, chartSettings, zoomManager)
break
case "pie":
this.chart = new PieChart(this.canvas, data, graphSettings)
this.chart = new PieChart(this.canvas, data, chartSettings, zoomManager)
break
case "donut":
this.chart = new DonutChart(this.canvas, data, graphSettings)
this.chart = new DonutChart(this.canvas, data, chartSettings, zoomManager)
break
case "bar":
this.chart = new BarChart(this.canvas, data, graphSettings)
this.chart = new BarChart(this.canvas, data, chartSettings, zoomManager)
break
case "area":
this.chart = new AreaChart(this.canvas, data, graphSettings)
this.chart = new AreaChart(this.canvas, data, chartSettings, zoomManager)
break
case "smootharea":
this.chart = new SmoothAreaChart(this.canvas, data, graphSettings)
this.chart = new SmoothAreaChart(this.canvas, data, chartSettings, zoomManager)
break
case "stacked":
this.chart = new StackedChart(this.canvas, data, graphSettings)
this.chart = new StackedChart(this.canvas, data, chartSettings, zoomManager)
break
}
setTimeout(this.chart.updateLegend(graphSettings.displayLegend, this.legend, this), 0)
setTimeout(this.chart.updateLegend(chartSettings.displayLegend, this.legend, this), 0)
this.chart.resizeCanvas(this.parent, this.legend.offsetHeight)
this.chart.draw()

@ -8,9 +8,11 @@ class AreaChart extends PointChart {
* @param {HTMLCanvasElement} canvas - The canvas element to draw the chart on
* @param {Array<Object>} data - The data to visualize on the chart
* @param {Object} settings - The settings for the chart
*/
constructor(canvas, data, settings) {
super(canvas, data, settings)
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings, zoom) {
// Call the constructor of the parent class (Chart)
super(canvas, data, settings, zoom)
}
/**
@ -35,17 +37,16 @@ class AreaChart extends PointChart {
if (categ.values[i] === null) continue
// Calculate the x and y coordinates for the current value
let x = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i
let y = this.bounds.xAxis - categ.values[i] / this.extreme * this.scale
xmax = x
let pos = this.getPointPos(i, categ.values[i])
xmax = pos.x
this.ctx.lineTo(x, y)
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.bounds.xAxis)
this.ctx.lineTo(this.bounds.left, this.bounds.xAxis)
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()

@ -8,9 +8,11 @@ class BarChart extends Chart {
* @param {HTMLCanvasElement} canvas - The canvas element to draw the chart on
* @param {Array<Object>} data - The data to visualize on the chart
* @param {Object} settings - The settings for the chart
*/
constructor(canvas, data, settings) {
super(canvas, data, settings)
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings, zoom) {
// Call the constructor of the parent class (Chart)
super(canvas, data, settings, zoom)
}
/**
@ -18,6 +20,7 @@ class BarChart extends Chart {
*/
draw() {
// Calculate the number of bars/categories in the chart
this.clear()
let barCount = this.data.length
// Draw the axis without displaying axis values
@ -40,11 +43,11 @@ class BarChart extends Chart {
// Value of the bar
let value = categ.values[i]
// The left position of the bar in section
let left = this.bounds.left + (size * (i + 0.15) + (innerSize * num / barCount))
let left = this.zoomBounds.left + (size * (i + 0.15) + (innerSize * num / barCount)) * this.zoom.scaleX
// The height of the bar relative to the chart scale
let bar_height = value / this.extreme * this.scale
let bar_height = value / this.extreme * this.scale * this.zoom.scaleY
// The top position of the bar
let top = this.bounds.xAxis - categ.values[i] / this.extreme * this.scale
let top = this.zoomBounds.xAxis - categ.values[i] / this.extreme * this.scale * this.zoom.scaleY
// Draw x-axis labels
// Only for first category to avoid repeated drawings
@ -55,18 +58,28 @@ class BarChart extends Chart {
this.ctx.font = "16px Arial"
this.ctx.fillStyle = "black"
this.ctx.textAlign = "center"
this.ctx.fillText(text, this.bounds.width / this.dataLen * i + size / 2 + this.bounds.left, this.bounds.bottom + 15)
this.ctx.fillText(text, this.zoomBounds.width / this.dataLen * i + size / 2 + this.zoomBounds.left, this.bounds.bottom + 15)
this.ctx.stroke()
this.ctx.closePath()
}
// Set the fill color and draw the rectangle for the current bar
// Increment the count of bars
num++
this.ctx.fillStyle = categ.color
this.ctx.lineWidth = 0
let new_object = new Rectangle(this.ctx, value, colId, left, top, bar_width, bar_height)
new_object.draw()
this.objects.push(new_object)
// Create a new Rectangle object representing the current bar
let newObject = new Rectangle(this.ctx, value, colId, left, top, bar_width, bar_height)
// Add the new Rectangle object to the list of objects
this.objects.push(newObject)
// Check if the center of the new bar is within the bounds
if (this.isInBounds(newObject.getCenter())) {
// Set the fill color to the category color
this.ctx.fillStyle = categ.color
// Set the line width to 0 to prevent drawing the border
this.ctx.lineWidth = 0
// Draw the filled rectangle representing the current bar
newObject.draw()
}
})
}
}

@ -44,7 +44,13 @@ function getSmallest(data) {
return smallest
}
// Lighten or darken a hex color
/**
* Lighten or darken a hex color.
*
* @param {string} color - The hex color code.
* @param {number} amount - The amount to lighten or darken the color by.
* @returns {string} The adjusted hex color.
*/
function adjustColor(color, amount) {
return '#' + color.replace(/^#/, '').replace(/../g, color => ('0' + Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2))
}
@ -59,11 +65,13 @@ class Chart {
* @param {HTMLCanvasElement} canvas - The canvas element to draw the chart on
* @param {Array<Object>} data - The data to visualize on the chart
* @param {Object} settings - The settings for the chart
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings) {
constructor(canvas, data, settings, zoom) {
this.data = data // The data to visualize on the chart
this.settings = settings // The settings for the chart
this.canvas = canvas // The canvas element to draw the chart on
this.zoom = zoom // The zoom manager for the chart.
this.ctx = canvas.getContext("2d") // The 2D drawing context for the canvas
// Calculate the largest and smallest values from the data
@ -74,7 +82,7 @@ class Chart {
this.dataLen = Math.max( // get max index
...data.map(category => // from each category
Object.keys(category.values)[Object.keys(category.values).length - 1] // gets last(largest) key
)) + 1 // indexes start at zero, so the length is +1
)) + 1 // indexes start at zero, so the length is +1
// Update the bounds of the chart based on the canvas size and margins
this.updateBounds()
@ -93,6 +101,8 @@ class Chart {
this.scale = this.bounds.height
- (this.largest >= 0 ? (this.bounds.bottom - this.bounds.xAxis) : 0)
this.extreme = this.largest <= 0 ? Math.abs(this.smallest) : Math.abs(this.largest)
this.zoomBounds = this.getZoomBounds()
}
/**
@ -128,10 +138,51 @@ class Chart {
// between the two points on the graph area.
result.xAxis = result.bottom
- result.height / ((Math.abs(this.largest)) + Math.abs(this.smallest)) * Math.abs(this.smallest)
return result
}
/**
* Retrieves the zoomed bounds of the chart.
* @returns {Object} An object containing zoomed bounds information.
*/
getZoomBounds() {
let result = {}
// Get screen coordinates of the top-left corner of the bounds
let pos = this.zoom.worldToScreen(this.bounds.left, this.bounds.top)
result.left = pos.x
result.top = pos.y
// Get screen coordinates of the bottom-right corner of the bounds
pos = this.zoom.worldToScreen(this.bounds.right, this.bounds.bottom)
result.right = pos.x
result.bottom = pos.y
// Calculate width and height of the zoomed bounds
result.width = this.bounds.width * this.zoom.scaleX
result.height = this.bounds.height * this.zoom.scaleY
// Get the screen coordinate of the x-axis
result.xAxis = this.zoom.worldToScreen(null, this.bounds.xAxis).y
return result
}
/**
* Checks if a given position is within the bounds.
*
* @param {Object} pos - The position to check with x and y coordinates.
* @returns {boolean} Returns true if the position is within the bounds, false otherwise.
*/
isInBounds(pos) {
return !(
pos.x < this.bounds.left ||
pos.x > this.bounds.right ||
pos.y < this.bounds.top ||
pos.y > this.bounds.bottom)
}
/**
* Draw the title on the canvas.
*/
@ -252,13 +303,16 @@ class Chart {
ctx.fill()
this.objects.forEach(object => {
// Encode shape index into color
let color = "#" + object.index.toString(16).padStart(6, '0')
ctx.fillStyle = color
ctx.strokeStyle = color
// Draw the shape
object.draw(ctx)
// Only draw objects on screen
if (this.isInBounds(object.getCenter())) {
// Encode shape index into color
let color = "#" + object.index.toString(16).padStart(6, '0')
ctx.fillStyle = color
ctx.strokeStyle = color
// Draw the shape
object.draw(ctx)
}
})
}
@ -272,8 +326,6 @@ class Chart {
// Clear the entire canvas to prepare for redrawing
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
ctx.fill()
ctx.scale(2, 2)
ctx.shadowBlur = 15
objects.forEach(object => {
@ -293,8 +345,6 @@ class Chart {
// Stroke to draw the border
ctx.stroke()
})
ctx.scale(0.5, 0.5)
}
/**
@ -325,6 +375,11 @@ class Chart {
// Draw the title if enabled
if (this.settings.displayTitle)
this.drawTitle()
this.ctx.beginPath()
this.ctx.rect(this.bounds.left, this.bounds.top, this.bounds.width, this.bounds.height)
this.ctx.clip()
this.ctx.closePath()
}
/**
@ -341,18 +396,23 @@ class Chart {
this.ctx.lineWidth = 1
// Calculate the Y-position for the tick
let yPos = Math.round(this.bounds.xAxis - i * this.scale / this.extreme)
let y = this.bounds.xAxis - i * this.scale / this.extreme
y = this.zoom.worldToScreen(null, y).y
// Skip text and lines if out of bounds
if (y < this.bounds.top || y > this.bounds.bottom)
continue
// Draw support lines if enabled
if (this.settings.displaySupportLines) {
this.ctx.moveTo(this.bounds.left, yPos)
this.ctx.lineTo(this.bounds.right, yPos)
this.ctx.moveTo(this.bounds.left, y)
this.ctx.lineTo(this.bounds.right, y)
}
// Set text style and alignment for the Y-axis values
this.ctx.fillStyle = "black"
this.ctx.textAlign = "end"
this.ctx.fillText(i, this.bounds.left - 3, yPos)
this.ctx.fillText(i, this.bounds.left - 3, y)
// Draw the tick
this.ctx.stroke()
@ -374,13 +434,18 @@ class Chart {
// Loop through the data to draw X-axis ticks and labels
for (let i = 0; i < this.dataLen; i++) {
// Calculate the X-position for the tick
let x = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i
let x = this.zoomBounds.left + this.zoomBounds.width / (this.dataLen - 1) * i
let y = this.bounds.bottom + 18
// Skip text if out of bounds
if (x < this.bounds.left || x > this.bounds.right)
continue
// Set the text for the X-axis label
let text = (i + 1).toString()
// Draw the X-axis label
this.ctx.fillText(text, x, this.bounds.bottom + 18)
this.ctx.fillText(text, x, y)
}
this.ctx.closePath()
}
@ -401,8 +466,14 @@ class Chart {
this.ctx.lineTo(this.bounds.left, this.bounds.bottom)
// Draw the horizontal X-axis line
this.ctx.moveTo(this.bounds.left, this.bounds.xAxis)
this.ctx.lineTo(this.bounds.right, this.bounds.xAxis)
let y = this.zoomBounds.xAxis
if (y < this.bounds.top)
y = this.bounds.top
if (y > this.bounds.bottom)
y = this.bounds.bottom
this.ctx.moveTo(this.bounds.left, y)
this.ctx.lineTo(this.bounds.right, y)
this.ctx.stroke()
}

@ -8,9 +8,11 @@ class DonutChart extends Chart {
* @param {HTMLCanvasElement} canvas - The canvas element to draw the chart on
* @param {Array<Object>} data - The data to visualize on the chart
* @param {Object} settings - The settings for the chart
*/
constructor(canvas, data, settings) {
super(canvas, data, settings)
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings, zoom) {
// Call the constructor of the parent class (Chart)
super(canvas, data, settings, zoom)
}
/**

@ -8,9 +8,11 @@ class LineChart extends PointChart {
* @param {HTMLCanvasElement} canvas - The canvas element to draw the chart on
* @param {Array<Object>} data - The data to visualize on the chart
* @param {Object} settings - The settings for the chart
*/
constructor(canvas, data, settings) {
super(canvas, data, settings)
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings, zoom) {
// Call the constructor of the parent class (Chart)
super(canvas, data, settings, zoom)
}
/**
@ -33,10 +35,9 @@ class LineChart extends PointChart {
if (categ.values[i] === null) continue
// Calculate the x and y coordinates for the current value
let x = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i
let y = this.bounds.xAxis - categ.values[i] / this.extreme * this.scale
let pos = this.getPointPos(i, categ.values[i])
this.ctx.lineTo(x, y)
this.ctx.lineTo(pos.x, pos.y)
}
this.ctx.stroke()
this.ctx.closePath()

@ -8,9 +8,11 @@ class PieChart extends Chart {
* @param {HTMLCanvasElement} canvas - The canvas element to draw the chart on
* @param {Array<Object>} data - The data to visualize on the chart
* @param {Object} settings - The settings for the chart
*/
constructor(canvas, data, settings) {
super(canvas, data, settings)
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings, zoom) {
// Call the constructor of the parent class (Chart)
super(canvas, data, settings, zoom)
}
/**

@ -7,10 +7,18 @@ class PointChart extends Chart {
* @param {HTMLElement} canvas - The canvas element to draw the chart on
* @param {Array} data - The data array containing values for the chart
* @param {Object} settings - The settings object for configuring the chart
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings) {
constructor(canvas, data, settings, zoom) {
// Call the constructor of the parent class (Chart)
super(canvas, data, settings)
super(canvas, data, settings, zoom)
}
getPointPos(i, value) {
return {
x: Math.round(this.zoomBounds.left + this.zoomBounds.width / (this.dataLen - 1) * i),
y: Math.round(this.zoomBounds.xAxis - value / this.extreme * this.scale * this.zoom.scaleY)
}
}
/**
@ -40,17 +48,22 @@ class PointChart extends Chart {
continue
// Calculate the x and y coordinates for the data point
let x = Math.round(this.bounds.left + this.bounds.width / (this.dataLen - 1) * i)
let y = Math.round(this.bounds.xAxis - values[i] / this.extreme * this.scale)
let pos = this.getPointPos(i, values[i])
// Create a new Circle object for the data point
let new_object = new Circle(this.ctx, values[i], name, x, y, this.settings.pointSize)
new_object.draw()
this.objects.push(new_object)
let shape = new Circle(this.ctx, values[i], name, pos.x, pos.y, this.settings.pointSize)
// Draw a border if enabled
if (this.settings.pointBorderSize > 0)
this.ctx.stroke()
// Don't draw objects outside of screen (zoom or panning)
if (this.isInBounds(shape.getCenter())) {
shape.draw(this.ctx)
// Draw a border if enabled
if (this.settings.pointBorderSize > 0)
this.ctx.stroke()
}
// Add the shape
this.objects.push(shape)
}
}
@ -68,6 +81,7 @@ class PointChart extends Chart {
console.time("1")
this.data.forEach((categ, colId) => {this.drawPoints(categ.values, colId, categ.color)})
console.timeEnd("1")
}, 0);
}, 0)
}
}

@ -65,6 +65,18 @@ class Rectangle extends Shape {
// Fill the rectangle with the current fill style
ctx.fill()
}
/**
* Returns the center coordinates of the rectangle.
*
* @returns {Object} Object containing x and y coordinates of the center.
*/
getCenter() {
return {
x: this.x + this.w / 2,
y: 50
}
}
}
/**
@ -91,13 +103,25 @@ class Circle extends Shape {
*
* @param {CanvasRenderingContext2D} [ctx=this.ctx] - The 2D drawing context for the canvas
*/
draw(ctx = this.ctx) {
draw(ctx = this.ctx) {
ctx.beginPath()
// Define the circle
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI)
// Fill the circle with the current fill style
ctx.fill()
}
/**
* Returns the center coordinates of the circle.
*
* @returns {Object} Object containing x and y coordinates of the center.
*/
getCenter() {
return {
x: this.x,
y: this.y
}
}
}
/**
@ -138,6 +162,18 @@ class PieSlice extends Circle {
// Fill the pie slice with the current fill style
ctx.fill()
}
/**
* Returns the center coordinates of the pie slice.
*
* @returns {Object} Object containing x and y coordinates of the center.
*/
getCenter() {
return {
x: this.x,
y: this.y
}
}
}
/**
@ -178,4 +214,16 @@ class DonutSlice extends PieSlice {
// Fill the donut slice with the current fill style
ctx.fill()
}
/**
* Returns the center coordinates of the donut slice.
*
* @returns {Object} Object containing x and y coordinates of the center.
*/
getCenter() {
return {
x: this.x,
y: this.y
}
}
}

@ -8,9 +8,11 @@ class SmoothAreaChart extends PointChart {
* @param {HTMLCanvasElement} canvas - The canvas element to draw the chart on
* @param {Array<Object>} data - The data to visualize on the chart
* @param {Object} settings - The settings for the chart
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings) {
super(canvas, data, settings)
constructor(canvas, data, settings, zoom) {
// Call the constructor of the parent class (Chart)
super(canvas, data, settings, zoom)
}
/**
@ -29,36 +31,34 @@ class SmoothAreaChart extends PointChart {
this.ctx.strokeStyle = categ.color
// Calculate the starting point for the line
let x = this.bounds.left
let y = (this.bounds.xAxis - categ.values[0] / this.extreme * this.scale)
this.ctx.moveTo(x, y)
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 coodrinates
let x1 = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i
let y1 = this.bounds.xAxis - categ.values[i] / this.extreme * this.scale
// Calculate left point coordinates
// Calculate right point coodrinates
let x2 = this.bounds.left + this.bounds.width / (this.dataLen - 1) * (i + 1)
let y2 = this.bounds.xAxis - categ.values[i + 1] / this.extreme * this.scale
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 = (x1 + x2) / 2
let xm = (leftPos.x + rightPos.x) / 2
// Find quarter points
let xl = (x1 + xm) / 2
let xr = (x2 + xm) / 2
let xl = (leftPos.x + xm) / 2
let xr = (rightPos.x + xm) / 2
// Draw a curve that smoothly connects the points
this.ctx.bezierCurveTo(xl, y1, xr, y2, x2, y2);
xmax = x2
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.bounds.xAxis)
this.ctx.lineTo(this.bounds.left, this.bounds.xAxis)
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

@ -8,9 +8,11 @@ class SmoothLineChart extends PointChart {
* @param {HTMLCanvasElement} canvas - The canvas element to draw the chart on
* @param {Array<Object>} data - The data to visualize on the chart
* @param {Object} settings - The settings for the chart
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings) {
super(canvas, data, settings)
constructor(canvas, data, settings, zoom) {
// Call the constructor of the parent class (Chart)
super(canvas, data, settings, zoom)
}
/**
@ -29,28 +31,25 @@ class SmoothLineChart extends PointChart {
this.ctx.strokeStyle = categ.color
// Calculate the starting point for the line
let x = this.bounds.left
let y = (this.bounds.xAxis - categ.values[0] / this.extreme * this.scale)
this.ctx.moveTo(x, y)
let pos1 = this.getPointPos(0, categ.values[0])
this.ctx.moveTo(pos1.x, pos1.y)
// Draw the Bezier curve for the smooth line
for (let i = 0; i < this.dataLen - 1; i++) {
// Calculate left point coodrinates
let x1 = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i
let y1 = this.bounds.xAxis - categ.values[i] / this.extreme * this.scale
// Calculate left point coordinates
let leftPos = this.getPointPos(i, categ.values[i])
// Calculate right point coodrinates
let x2 = this.bounds.left + this.bounds.width / (this.dataLen - 1) * (i + 1)
let y2 = this.bounds.xAxis - categ.values[i + 1] / this.extreme * this.scale
// Calculate right point coordinates
let rightPos = this.getPointPos(i + 1, categ.values[i + 1])
// Find middle point
let xm = (x1 + x2) / 2
let xm = (leftPos.x + rightPos.x) / 2
// Find quarter points
let xl = (x1 + xm) / 2
let xr = (x2 + xm) / 2
let xl = (leftPos.x + xm) / 2
let xr = (rightPos.x + xm) / 2
// Draw a curve that smoothly connects the points
this.ctx.bezierCurveTo(xl, y1, xr, y2, x2, y2);
this.ctx.bezierCurveTo(xl, leftPos.y, xr, rightPos.y, rightPos.x, rightPos.y);
}
this.ctx.stroke()
this.ctx.closePath()

@ -8,9 +8,11 @@ class StackedChart extends Chart {
* @param {HTMLCanvasElement} canvas - The canvas element to draw the chart on
* @param {Array<Object>} data - The data to visualize on the chart
* @param {Object} settings - The settings for the chart
* @param {ZoomManager} zoom - The zoom manager for the chart.
*/
constructor(canvas, data, settings) {
super(canvas, data, settings)
constructor(canvas, data, settings, zoom) {
// Call the constructor of the parent class (Chart)
super(canvas, data, settings, zoom)
}
/**
@ -38,14 +40,14 @@ class StackedChart extends Chart {
this.drawAxis(false)
// Calculate the size of each bar segment based on the canvas width and number of data points
let size = this.bounds.width / this.dataLen
let size = this.zoomBounds.width / this.dataLen
// Calculate the actual width of each bar, making it 70% of the calculated size
let bar_width = size * 0.7
// Iterate over each data point to draw the stacked bars
for (let i = 0; i < this.dataLen; i++) {
// The top position of the last stacked bar segment
let last_top = this.bounds.xAxis
let last_top = this.zoomBounds.xAxis
// Counter to determine the first category in each data point
let num = 0
@ -53,9 +55,9 @@ class StackedChart extends Chart {
// Value of the bar segment
let value = categ.values[i]
// The height of the bar segment relative to the largest total value
let bar_height = value / largest * this.bounds.height
let bar_height = value / largest * this.zoomBounds.height
// The left position of the bar segment
let left = this.bounds.left + size * (i + 0.15)
let left = this.zoomBounds.left + size * (i + 0.15)
// The top position of the bar segment
let top = last_top - bar_height
@ -73,17 +75,29 @@ class StackedChart extends Chart {
this.ctx.font = "16px Arial"
this.ctx.fillStyle = "black"
this.ctx.textAlign = "center"
this.ctx.fillText(text, this.bounds.width / this.dataLen * i + size / 2 + this.bounds.left, this.bounds.bottom + 15)
this.ctx.fillText(text, this.zoomBounds.width / this.dataLen * i + size / 2 + this.zoomBounds.left, this.zoomBounds.bottom + 15)
this.ctx.stroke()
this.ctx.closePath()
}
num++
// Set the fill color and draw the rectangle for the current bar segment
this.ctx.fillStyle = categ.color
let new_object = new Rectangle(this.ctx, value, colId, left, top, bar_width, bar_height)
new_object.draw()
this.objects.push(new_object)
// Increment the count of bars
num++
// Create a new Rectangle object representing the current bar
let newObject = new Rectangle(this.ctx, value, colId, left, top, bar_width, bar_height)
// Add the new Rectangle object to the list of objects
this.objects.push(newObject)
// Check if the center of the new bar is within the bounds
if (this.isInBounds(newObject.getCenter())) {
// Set the fill color to the category color
this.ctx.fillStyle = categ.color
// Set the line width to 0 to prevent drawing the border
this.ctx.lineWidth = 0
// Draw the filled rectangle representing the current bar
newObject.draw()
}
})
}
}

@ -0,0 +1,79 @@
class ZoomManager {
constructor(horizontalZoom, verticalZoom) {
this.x = 0
this.y = 0
this.scaleX = 1
this.scaleY = 1
this.horizontalZoom = horizontalZoom
this.verticalZoom = verticalZoom
}
/**
* Convert world coordinates to screen coordinates.
*
* @param {number} worldX - The x-coordinate in world space.
* @param {number} worldY - The y-coordinate in world space.
* @returns {Array} The screen coordinates [screenX, screenY].
*/
worldToScreen(worldX, worldY) {
let screenX = (worldX - this.x) * this.scaleX
let screenY = (worldY - this.y) * this.scaleY
return {
x: screenX,
y: screenY
}
}
/**
* Convert screen coordinates to world coordinates.
*
* @param {number} screenX - The x-coordinate on the screen.
* @param {number} screenY - The y-coordinate on the screen.
* @returns {Array} The world coordinates [worldX, worldY].
*/
screenToWorld(screenX, screenY) {
let worldX = screenX / this.scaleX + this.x
let worldY = screenY / this.scaleY + this.y
return {
x: worldX,
y: worldY
}
}
/**
* Recalculates zoom based on mouse wheel event.
* @param {MouseEvent} event - The mouse wheel event object.
*/
recalculate(event) {
// Get mouse position
const pos = {
x: event.clientX,
y: event.clientY
}
// Calculate world coordinates before zoom
let beforeZoom, afterZoom
beforeZoom = this.screenToWorld(pos.x, pos.y)
// Adjust zoom scale based on mouse wheel delta
if (this.horizontalZoom)
this.scaleX -= (10 * this.scaleX) / event.deltaY;
if (this.verticalZoom)
this.scaleY -= (10 * this.scaleY) / event.deltaY;
// Calculate world coordinates after zoom
afterZoom = this.screenToWorld(pos.x, pos.y)
// Adjust zoom position to keep zoom centered around mouse position
this.x += beforeZoom.x - afterZoom.x
this.y += beforeZoom.y - afterZoom.y
// Reset zoom if it goes below 1
if (this.scaleX < 1 || this.scaleY < 1) {
this.x = 0
this.y = 0
this.scaleX = 1
this.scaleY = 1
}
}
}

@ -133,6 +133,22 @@ class MetadataType extends AbstractType
'required' => false,
])
)
// Zoom settings
->add(
$builder->create('group5', FormType::class, [
'inherit_data' => true,
'label' => 'Zoom Settings',
'label_attr' => ['class' => 'submenuLabel']
])
->add('horizontalZoom', CheckboxType::class, [
'label' => 'Enable horizontal zoom',
'required' => false,
])
->add('verticalZoom', CheckboxType::class, [
'label' => 'Enable vertical zoom',
'required' => false,
])
)
->add('backgroundColor', ColorType::class, [
'label' => 'Background color',
'required' => false,

@ -11,6 +11,7 @@
{% block javascripts %}
{{ parent() }}
<script type="text/javascript" src={{ asset('/scripts/zoom_manager.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/primitives.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/point_chart.js') }}></script>

@ -19,7 +19,7 @@
<main>
{{ form_start(form) }}
<div id="mainDiv">
<object id="graphDiv" data={{ "https://spacek.blue/charts/" ~ field_value(form.code) }}></object>
<iframe id="graphDiv" src={{ "https://spacek.blue/charts/" ~ field_value(form.code) }}></iframe>
<div id="settings_div">
{{ form_row(form.name) }}
{{ form_row(form.code) }}

Loading…
Cancel
Save

Powered by TurnKey Linux.