317 lines
8.0 KiB
JavaScript
317 lines
8.0 KiB
JavaScript
/* This code is copywrite Telos Digital 2025 working for Telos Partners:
|
|
|
|
https://www.telospartners.com/
|
|
|
|
It is released under the MIT licence https://opensource.org/license/mit
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the “Software”), to
|
|
deal in the Software without restriction, including without limitation the
|
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
sell copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
function drawBar(id,data) {
|
|
console.log(id);
|
|
console.log(data);
|
|
const svg = d3.select(`${id}`);
|
|
const backgroundwidth = +svg.attr("width");
|
|
const backgroundradius = 10;
|
|
const width = 600; //+svg.attr("width");
|
|
const height = 200; //+svg.attr("height");
|
|
const margin = { top: 40, right: 30, bottom: 60, left: 30 };
|
|
|
|
// Draw the white background
|
|
// Draw the rounded rectangle
|
|
svg.append("rect")
|
|
.attr("x", 0)
|
|
.attr("y", 0)
|
|
.attr("width", backgroundwidth)
|
|
.attr("height", height)
|
|
.attr("rx", backgroundradius)
|
|
.attr("ry", backgroundradius)
|
|
.attr("fill", "white");
|
|
// .attr("stroke", "white")
|
|
// .attr("stroke-width", 2);
|
|
|
|
|
|
//const data = {
|
|
/*
|
|
data = {
|
|
"-3": 0,
|
|
"-2": 1,
|
|
"-1": 3,
|
|
"0": 0,
|
|
"1": 2,
|
|
"2": 0,
|
|
"3": 10
|
|
};
|
|
*/
|
|
//console.log(data);
|
|
|
|
|
|
const dataArray = Object.entries(data).map(([key, count]) => ({
|
|
key: +key,
|
|
count: count
|
|
}));
|
|
|
|
const x = d3.scaleBand()
|
|
.domain([-3,-2,-1,0,1,2,3])
|
|
.range([margin.left, width - margin.right])
|
|
.padding(0);
|
|
|
|
const y = d3.scaleLinear()
|
|
.domain([0, d3.max(dataArray, d => d.count)])
|
|
.nice()
|
|
.range([height - margin.bottom, margin.top]);
|
|
|
|
// X Axis
|
|
svg.append("g")
|
|
.attr("transform", `translate(0,${height - margin.bottom})`)
|
|
.call(d3.axisBottom(x)
|
|
.tickSize(0))
|
|
.selectAll("text")
|
|
.attr("x", 0)
|
|
.attr("y", 10)
|
|
.style("text-anchor", "middle")
|
|
.style("font-size", "12px");
|
|
|
|
|
|
// Add the required ticks
|
|
const xTicks = d3.scaleLinear()
|
|
.domain([-3.5, 3.5])
|
|
.range([margin.left, width - margin.right]);
|
|
|
|
svg.append("g")
|
|
.attr("transform", `translate(0,${height - margin.bottom})`)
|
|
.call(d3.axisBottom(xTicks).tickValues(d3.range(-3.5, 4, 1)))
|
|
.selectAll("text")
|
|
.style("visibility", "hidden"); // This hides the labels
|
|
|
|
// Y Axis
|
|
//svg.append("g")
|
|
// .attr("transform", `translate(${margin.left},0)`)
|
|
// .call(d3.axisLeft(y).ticks(5));
|
|
|
|
// Bars
|
|
svg.selectAll(".bar")
|
|
.data(dataArray)
|
|
.enter()
|
|
.append("rect")
|
|
.attr("class", "bar")
|
|
.attr("x", d => x(d.key))
|
|
.attr("y", d => y(d.count))
|
|
.attr("width", x.bandwidth())
|
|
.attr("height", d => y(0) - y(d.count))
|
|
.attr("fill", "#003c4b"); // From brand guidelines
|
|
|
|
// Labels
|
|
svg.selectAll(".label")
|
|
.data(dataArray)
|
|
.enter()
|
|
.append("text")
|
|
.attr("x", d => x(d.key) + x.bandwidth() / 2)
|
|
.attr("y", d => d.count > 0 ? y(d.count) - 5 : y(0))
|
|
.attr("text-anchor", "middle")
|
|
.attr("font-size", "12px")
|
|
.attr("font-style", "italic")
|
|
.attr("fill", "black")
|
|
.text(d => d.count > 0 ? d.count : "");
|
|
|
|
// Average line
|
|
const format = d3.format(".2f");
|
|
const totalCount = d3.sum(dataArray, d => d.count);
|
|
const weightedSum = d3.sum(dataArray, d => d.key * d.count);
|
|
average = totalCount > 0 ? weightedSum / totalCount : 0;
|
|
|
|
const xLinear = d3.scaleLinear()
|
|
.domain([-3, 3])
|
|
.range([margin.left + x.bandwidth() / 2, width - margin.right - x.bandwidth() / 2]);
|
|
|
|
const avgX = xLinear(average);
|
|
//const avgX = xLinear(-0.50); // Temporary for testing
|
|
|
|
svg.append("line")
|
|
.attr("x1", avgX)
|
|
.attr("x2", avgX)
|
|
.attr("y1", y(0)+20)
|
|
.attr("y2", y(d3.max(dataArray, d => d.count)) - 20)
|
|
.attr("stroke", "#e40074") // From brand guidelines
|
|
.attr("stroke-width", 4)
|
|
.attr("stroke-dasharray", "10,4");
|
|
|
|
/*
|
|
svg.append("text")
|
|
.attr("x", avgX)
|
|
.attr("y", y(d3.max(dataArray, d => d.count)) - 30)
|
|
.attr("text-anchor", "middle")
|
|
.attr("fill", "#e40074")
|
|
.attr("font-size", "12px")
|
|
.text("Average ("+average+")");
|
|
*/
|
|
|
|
// Title beneath the graph
|
|
/*
|
|
svg.append("text")
|
|
.attr("x", width / 2)
|
|
.attr("y", height - 10) // Just below the x-axis
|
|
.attr("text-anchor", "middle")
|
|
.attr("font-size", "14px")
|
|
.attr("fill", "black")
|
|
.text("Demo bar graph");
|
|
*/
|
|
|
|
|
|
// Add info to right of bar chart
|
|
// Oblong settings
|
|
const boxx = 610;
|
|
const boxy = 60;
|
|
const boxwidth = 170;
|
|
const boxheight = 80;
|
|
const cornerRadius = 10;
|
|
|
|
// Draw the rounded rectangle
|
|
svg.append("rect")
|
|
.attr("x", boxx)
|
|
.attr("y", boxy)
|
|
.attr("width", boxwidth)
|
|
.attr("height", boxheight)
|
|
.attr("rx", cornerRadius)
|
|
.attr("ry", cornerRadius)
|
|
.attr("fill", "white")
|
|
.attr("stroke", "#333")
|
|
.attr("stroke-width", 2);
|
|
|
|
// Draw the horizontal divider
|
|
svg.append("line")
|
|
.attr("x1", boxx)
|
|
.attr("x2", boxx + boxwidth)
|
|
.attr("y1", boxy + boxheight / 2)
|
|
.attr("y2", boxy + boxheight / 2)
|
|
.attr("stroke", "#333")
|
|
.attr("stroke-width", 1);
|
|
|
|
// Add text to the top half
|
|
svg.append("text")
|
|
.attr("x", boxx + boxwidth / 2)
|
|
.attr("y", boxy + boxheight / 4)
|
|
.attr("text-anchor", "middle")
|
|
.attr("dominant-baseline", "middle")
|
|
.attr("font-size", "20px")
|
|
.attr("font-weight", "400") // 400 is normal, 700 is bold, 900 is extra-bold
|
|
.attr("font-family", "sans-serif")
|
|
.text("Average: ")
|
|
.append(function() {
|
|
return document.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
|
})
|
|
.text(average.toFixed(1))
|
|
.attr("class", "average")
|
|
.attr("fill", "#e40074")
|
|
.attr("font-weight", "900");
|
|
|
|
|
|
// .text("Average : "+average.toFixed(2));
|
|
|
|
// Get the score gap:
|
|
|
|
let maxKey = null;
|
|
let minKey = null;
|
|
|
|
for (const [key, value] of Object.entries(data)) {
|
|
if (value > 0) {
|
|
const numKey = Number(key);
|
|
//console.log(numkey);
|
|
if (maxKey === null || numKey > maxKey) {
|
|
maxKey = numKey;
|
|
}
|
|
if (minKey === null || numKey < minKey) {
|
|
minKey = numKey;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
scoregap = maxKey - minKey+1;
|
|
|
|
// Add text to the bottom half
|
|
svg.append("text")
|
|
.attr("x", boxx + boxwidth / 2)
|
|
.attr("y", boxy + 3 * boxheight / 4)
|
|
.attr("text-anchor", "middle")
|
|
.attr("dominant-baseline", "middle")
|
|
.attr("font-size", "20px")
|
|
.attr("font-weight", "400") // 400 is normal, 700 is bold, 900 is extra-bold
|
|
.attr("font-family", "sans-serif")
|
|
.text("Score gap: ")
|
|
.append(function() {
|
|
return document.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
|
})
|
|
.text(scoregap)
|
|
.attr("font-weight", "900");
|
|
|
|
|
|
|
|
|
|
// .text("Score Gap : "+scoregap);
|
|
|
|
|
|
return average; //Needed for the next layer up in the analysis
|
|
|
|
}
|
|
|
|
|
|
|
|
function doBarData(id,qid, survey, Q1=0, Q2=0, Q3=0 ) {
|
|
// Create the form data object
|
|
const formData = new FormData();
|
|
formData.append('qid', qid);
|
|
formData.append('survey', survey);
|
|
formData.append('Q1', Q1);
|
|
formData.append('Q2', Q2);
|
|
formData.append('Q3', Q3);
|
|
|
|
// Send the form data using fetch
|
|
fetch('get_qid_counts.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
// Output result
|
|
console.log(data);
|
|
const parsed = JSON.parse(data);
|
|
|
|
// 2. Clean the keys and build a usable object
|
|
const bardata = {};
|
|
parsed.forEach(entry => {
|
|
const rawKey = Object.keys(entry)[0];
|
|
const cleanKey = rawKey.replace(/"/g, ''); // Remove extra quotes
|
|
bardata[cleanKey] = entry[rawKey];
|
|
});
|
|
|
|
console.log(bardata);
|
|
drawBar(id,bardata);
|
|
// return data; // Should be an array like [{ value: -3, count: 2 }, ..., { value: 3, count: 5 }]
|
|
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
});
|
|
}
|