Latest updates
This commit is contained in:
parent
3eb2bb0491
commit
9d6c761d15
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"idf.pythonInstallPath": "/usr/bin/python"
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<script src="./drawtriangle.js"></script>
|
||||
<script src="./drawtriangleinverted.js"></script>
|
||||
<script src="./drawbar.js"></script>
|
||||
<script src="./savesvg.js"></script>
|
||||
|
||||
<style>
|
||||
@ -13,7 +14,7 @@
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: blue;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
svg {
|
||||
@ -57,22 +58,25 @@
|
||||
drawtriangle('#svg1','Roles','#008845',[0.5,0.6,0.5],'red', { x: 0, y: 350 },-0.7);
|
||||
drawtriangle('#svg1','Actions','#b2c8c4',[0.1,0.6,0.5],'green',{ x: 370, y: 350 },2.3);
|
||||
drawtriangle('#svg1','Approach','#ed4c0c',[0.3,0.6,0.5],amber,{ x: 185, y: 30 },1.4);
|
||||
drawtriangleinverted('#svg1','Impact','#74469c',[0.7,0.6,0.8],'',{ x: 185, y: 243},2.4);
|
||||
drawtriangleinverted('#svg1','Impact','#74469c',[0.7,0.6,0.8],'green',{ x: 185, y: 243},2.4);
|
||||
makeSvgRightClickable('svg1');
|
||||
|
||||
|
||||
|
||||
doLittleWhiteTriangle('svg2');
|
||||
drawtriangle('#svg2','Roles','#008845',[0.5,0.6,0.5],'red', { x: 0, y: 100 },-0.7);
|
||||
drawtriangle('#svg2','Roles','#008845',[0.5,0.6,0.5],'', { x: 0, y: 100 },-0.7);
|
||||
makeSvgRightClickable('svg2');
|
||||
|
||||
|
||||
drawBar('#svg3');
|
||||
makeSvgRightClickable('svg3');
|
||||
|
||||
">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1" width="1000" height="1000"></svg>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg2" width="500" height="500"></svg>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3" width="800" height="800"></svg>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
1507
JSONStructure.json
Normal file
1507
JSONStructure.json
Normal file
File diff suppressed because it is too large
Load Diff
2
Questions.json
Normal file
2
Questions.json
Normal file
File diff suppressed because one or more lines are too long
203
drawbar.js
Normal file
203
drawbar.js
Normal file
@ -0,0 +1,203 @@
|
||||
|
||||
|
||||
function drawBar(id) {
|
||||
|
||||
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 = {
|
||||
"-3": 0,
|
||||
"-2": 1,
|
||||
"-1": 3,
|
||||
"0": 0,
|
||||
"1": 2,
|
||||
"2": 0,
|
||||
"3": 10
|
||||
};
|
||||
|
||||
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);
|
||||
const average = totalCount > 0 ? format(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(2.7); // 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", "6,2");
|
||||
|
||||
/*
|
||||
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 infor to right of bar chart
|
||||
// Oblong settings
|
||||
const boxx = 620;
|
||||
const boxy = 60;
|
||||
const boxwidth = 150;
|
||||
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", "900") // 400 is normal, 700 is bold, 900 is extra-bold
|
||||
.attr("font-family", "sans-serif")
|
||||
.text("Average : 1.69");
|
||||
|
||||
// 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", "900") // 400 is normal, 700 is bold, 900 is extra-bold
|
||||
.attr("font-family", "sans-serif")
|
||||
.text("Score Gap : 6");
|
||||
|
||||
}
|
@ -32,6 +32,8 @@ axes.forEach(d => d.angle = (d.angle - 90) * Math.PI / 180);
|
||||
// Sample data: [0-1] scale
|
||||
// const values = [0.8, 0.1, 0.76]; Now passed in function args
|
||||
|
||||
// Don't draw if class "no-axis"
|
||||
if (!svg.classed("no-axis")) {
|
||||
// Draw axis lines
|
||||
svg.append("g")
|
||||
.attr("class", "axis")
|
||||
@ -43,6 +45,9 @@ svg.append("g")
|
||||
.attr("x2", d => center.x + radius * Math.cos(d.angle))
|
||||
.attr("y2", d => center.y + radius * Math.sin(d.angle));
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Draw grid rings (polygons)
|
||||
const levels = 7;
|
||||
for (let i = 1; i <= levels; i++) {
|
||||
@ -60,9 +65,12 @@ for (let i = 1; i <= levels; i++) {
|
||||
|
||||
|
||||
// Draw axis ticks
|
||||
const tickLength = 5;
|
||||
let tickLength = 5;
|
||||
if (svg.classed("no-axis")) {
|
||||
tickLength = 0;
|
||||
}
|
||||
axes.forEach(axis => {
|
||||
for (let i = 1; i <= levels; i++) {
|
||||
for (let i = 1; i <= levels -1 ; i++) { // -1 to remove tick outside outline
|
||||
const r = (radius / levels) * i;
|
||||
const angle = axis.angle;
|
||||
|
||||
@ -112,7 +120,7 @@ svg.append("polygon")
|
||||
.attr("points", outerPoints.map(p => p.join(",")).join(" "))
|
||||
.attr("fill","none")
|
||||
.attr("stroke", colour) // Outer line color
|
||||
.attr("stroke-width", 9); // Thicker outer line
|
||||
.attr("stroke-width", 5); // Thicker outer line
|
||||
|
||||
// Put label in bottom centre of triangle
|
||||
const bottomLeft = outerPoints[1];
|
||||
@ -121,33 +129,35 @@ const bottomRight = outerPoints[2];
|
||||
const midX = (bottomLeft[0] + bottomRight[0]) / 2;
|
||||
const midY = (bottomLeft[1] + bottomRight[1]) / 2;
|
||||
|
||||
//Don't draw the label for 'no-text' class
|
||||
if (!svg.classed("no-text")){
|
||||
svg.append("text")
|
||||
.attr("x", midX)
|
||||
.attr("y", midY - 15) // Alter for correct height
|
||||
.attr("y", midY - 10) // Alter for correct height
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", textcolour) /// My Colour
|
||||
.attr("font-size", "22px")
|
||||
.attr("font-weight", "bold")
|
||||
.text(`${text}`);
|
||||
}
|
||||
|
||||
// And now a central number
|
||||
let bigy = midY-45;
|
||||
let bigfont = "36px";
|
||||
if (svg.classed("big-number")){
|
||||
bigy = midY-85;
|
||||
bigfont = "70px";
|
||||
}
|
||||
svg.append("text")
|
||||
.attr("x", midX)
|
||||
.attr("y", midY -60)
|
||||
.attr("y", bigy)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "black") // 🎨 customize color
|
||||
.attr("font-size", "24px")
|
||||
.attr("font-size", bigfont)
|
||||
.attr("font-weight", "bold")
|
||||
.attr("font-family", "Courier New, monospace")
|
||||
.text(`${mean}`);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Add axis labels
|
||||
/*
|
||||
svg.selectAll(".label")
|
||||
|
@ -1,6 +1,4 @@
|
||||
|
||||
|
||||
|
||||
function drawtriangleinverted(id,text,colour,values,rag,offset,mean)
|
||||
{
|
||||
|
||||
@ -34,7 +32,8 @@ axes.forEach(d => d.angle = (d.angle - 90) * Math.PI / 180);
|
||||
|
||||
// Sample data: [0-1] scale
|
||||
// const values = [0.8, 0.1, 0.76]; Now passed in function args
|
||||
|
||||
// Don't draw if class "no-axis"
|
||||
if (!svg.classed("no-axis")) {
|
||||
// Draw axis lines
|
||||
svg.append("g")
|
||||
.attr("class", "axis")
|
||||
@ -45,6 +44,9 @@ svg.append("g")
|
||||
.attr("y1", center.y)
|
||||
.attr("x2", d => center.x + radius * Math.cos(d.angle))
|
||||
.attr("y2", d => center.y + radius * Math.sin(d.angle));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Draw grid rings (polygons)
|
||||
const levels = 7;
|
||||
@ -61,10 +63,14 @@ for (let i = 1; i <= levels; i++) {
|
||||
.attr("points", points.map(p => p.join(",")).join(" "));
|
||||
}
|
||||
|
||||
|
||||
// Draw axis ticks
|
||||
const tickLength = 5;
|
||||
let tickLength = 5;
|
||||
if (svg.classed("no-axis")) {
|
||||
tickLength = 0;
|
||||
}
|
||||
axes.forEach(axis => {
|
||||
for (let i = 1; i <= levels; i++) {
|
||||
for (let i = 1; i <= levels - 1; i++) { // -1 so that tick doesn't stick out
|
||||
const r = (radius / levels) * i;
|
||||
const angle = axis.angle;
|
||||
|
||||
@ -114,7 +120,7 @@ svg.append("polygon")
|
||||
.attr("points", outerPoints.map(p => p.join(",")).join(" "))
|
||||
.attr("fill","none")
|
||||
.attr("stroke", colour) // Outer line color
|
||||
.attr("stroke-width", 9); // Thicker outer line
|
||||
.attr("stroke-width", 5); // Thicker outer line
|
||||
|
||||
// Put label in bottom centre of triangle (adjust for flat top)
|
||||
const bottomLeft = outerPoints[0];
|
||||
@ -123,6 +129,8 @@ const bottomRight = outerPoints[2];
|
||||
const midX = (bottomLeft[0] + bottomRight[0]) / 2;
|
||||
const midY = (bottomLeft[1] + bottomRight[1]) / 2;
|
||||
|
||||
//Don't draw the label for 'no-text' class
|
||||
if (!svg.classed("no-text")){
|
||||
svg.append("text")
|
||||
.attr("x", midX)
|
||||
.attr("y", midY + 30) // Adjust for correct height (moved to bottom)
|
||||
@ -131,14 +139,21 @@ svg.append("text")
|
||||
.attr("font-size", "22px")
|
||||
.attr("font-weight", "bold")
|
||||
.text(`${text}`);
|
||||
}
|
||||
|
||||
// And now a central number
|
||||
let bigy = midY-45;
|
||||
let bigfont = "36px";
|
||||
if (svg.classed("big-number")){
|
||||
bigy = midY-85;
|
||||
bigfont = "70px";
|
||||
}
|
||||
svg.append("text")
|
||||
.attr("x", midX)
|
||||
.attr("y", midY + 80)
|
||||
.attr("y", midY + 70)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "black") // 🎨 customize color
|
||||
.attr("font-size", "24px")
|
||||
.attr("font-size", bigfont)
|
||||
.attr("font-weight", "bold")
|
||||
.attr("font-family", "Courier New, monospace")
|
||||
.text(`${mean}`);
|
||||
|
BIN
eoq_code.tar
Normal file
BIN
eoq_code.tar
Normal file
Binary file not shown.
2072
eoq_code/Questions.json
Normal file
2072
eoq_code/Questions.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
eoq_code/config.php
Normal file
BIN
eoq_code/config.php
Normal file
Binary file not shown.
139
eoq_code/db.php
Normal file
139
eoq_code/db.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
// Settings
|
||||
$config = require 'config.php';
|
||||
|
||||
|
||||
$host = $config['db_host'];
|
||||
$db = $config['db_name'];
|
||||
$user = $config['db_user'];
|
||||
$pass = $config['db_pass'];
|
||||
echo $user.":".$pass.":".$host.":".$db;
|
||||
$charset = 'utf8mb4';
|
||||
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // throw exceptions on error
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // fetch results as associative arrays
|
||||
PDO::ATTR_EMULATE_PREPARES => false, // use native prepared statements
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $user, $pass, $options);
|
||||
} catch (\PDOException $e) {
|
||||
die("Database connection failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
// ...and now the functions
|
||||
|
||||
function getOrCreateSurvey($surveyId, $description = '') {
|
||||
global $pdo;
|
||||
|
||||
// Step 1: Check if survey exists
|
||||
$check = $pdo->prepare("SELECT id FROM Surveys WHERE surveyId = :surveyId");
|
||||
$check->execute(['surveyId' => $surveyId]);
|
||||
$existing = $check->fetch();
|
||||
|
||||
if ($existing) {
|
||||
return $existing['id'];
|
||||
}
|
||||
|
||||
// Step 2: Insert if not found
|
||||
$insert = $pdo->prepare("
|
||||
INSERT INTO Surveys (surveyId, description)
|
||||
VALUES (:surveyId, :description)
|
||||
");
|
||||
$insert->execute([
|
||||
'surveyId' => $surveyId,
|
||||
'description' => $description
|
||||
]);
|
||||
|
||||
|
||||
// Step 3: Get the id
|
||||
$check = $pdo->prepare("SELECT id FROM Surveys WHERE surveyId = :surveyId");
|
||||
$check->execute(['surveyId' => $surveyId]);
|
||||
$existing = $check->fetch();
|
||||
return $existing['id'];
|
||||
}
|
||||
|
||||
function upsertResponse($response) {
|
||||
global $pdo;
|
||||
|
||||
$sql = "
|
||||
INSERT INTO Responses (
|
||||
responseId, surveyId, startDate, endDate, status, ipAddress,
|
||||
progress, duration, finished, recordedDate, locationLatitude,
|
||||
locationLongitude, distributionChannel, userLanguage
|
||||
) VALUES (
|
||||
:responseId, :surveyId, :startDate, :endDate, :status, :ipAddress,
|
||||
:progress, :duration, :finished, :recordedDate, :locationLatitude,
|
||||
:locationLongitude, :distributionChannel, :userLanguage
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
surveyId = VALUES(surveyId),
|
||||
startDate = VALUES(startDate),
|
||||
endDate = VALUES(endDate),
|
||||
status = VALUES(status),
|
||||
ipAddress = VALUES(ipAddress),
|
||||
progress = VALUES(progress),
|
||||
duration = VALUES(duration),
|
||||
finished = VALUES(finished),
|
||||
recordedDate = VALUES(recordedDate),
|
||||
locationLatitude = VALUES(locationLatitude),
|
||||
locationLongitude = VALUES(locationLongitude),
|
||||
distributionChannel = VALUES(distributionChannel),
|
||||
userLanguage = VALUES(userLanguage)
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($response);
|
||||
|
||||
$selectStmt = $pdo->prepare("SELECT id FROM Responses WHERE responseId = :responseId");
|
||||
$selectStmt->execute(['responseId' => $response['responseId']]);
|
||||
$existing = $selectStmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($existing) {
|
||||
return $existing['id'];
|
||||
} else {
|
||||
return null; // not found, something unexpected happened
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function insertAnswers($pdo, $surveyId, $responseId, $answers) {
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO Answers (surveyId, responseId, QID, text, value)
|
||||
VALUES (:surveyId, :responseId, :QID, :text, :value)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
text = VALUES(text),
|
||||
value = VALUES(value)
|
||||
");
|
||||
|
||||
foreach ($answers as $key => $val) {
|
||||
if (strpos($key, 'QID') === 0) {
|
||||
$qid = $key;
|
||||
$text = null;
|
||||
$value = null;
|
||||
|
||||
// Handle numeric between -3 and +3
|
||||
if (is_numeric($val) && $val >= -3 && $val <= 3) {
|
||||
$value = (int) $val;
|
||||
} else {
|
||||
$text = substr($val, 0, 255); // enforce VARCHAR(255) limit
|
||||
}
|
||||
|
||||
$stmt->execute([
|
||||
'surveyId' => $surveyId,
|
||||
'responseId' => $responseId,
|
||||
'QID' => $qid,
|
||||
'text' => $text,
|
||||
'value' => $value
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
82
eoq_code/debug.html
Normal file
82
eoq_code/debug.html
Normal file
@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Spider Graph</title>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<style>
|
||||
svg {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.axis {
|
||||
stroke: #ccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.area {
|
||||
fill: steelblue;
|
||||
fill-opacity: 0.3;
|
||||
stroke: steelblue;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
.debug {
|
||||
fill: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg id="spider" width="300" height="300"></svg>
|
||||
|
||||
<script>
|
||||
const svg = d3.select("#spider");
|
||||
const width = +svg.attr("width");
|
||||
const height = +svg.attr("height");
|
||||
|
||||
const center = { x: width / 2, y: height / 2 };
|
||||
const radius = 100;
|
||||
|
||||
// Define 3 axes at 0, 120, 240 degrees in radians
|
||||
const axes = [
|
||||
{ angle: 0 },
|
||||
{ angle: 2 * Math.PI / 3 },
|
||||
{ angle: 4 * Math.PI / 3 }
|
||||
];
|
||||
|
||||
// Example values for each axis (normalized from 0 to 1)
|
||||
const values = [0.8, 0.6, 0.9];
|
||||
|
||||
// Draw axis lines
|
||||
svg.selectAll(".axis")
|
||||
.data(axes)
|
||||
.join("line")
|
||||
.attr("class", "axis")
|
||||
.attr("x1", center.x)
|
||||
.attr("y1", center.y)
|
||||
.attr("x2", d => center.x + radius * Math.cos(d.angle))
|
||||
.attr("y2", d => center.y + radius * Math.sin(d.angle));
|
||||
|
||||
// Convert values to coordinates
|
||||
const valuePoints = axes.map((d, i) => {
|
||||
const r = radius * values[i];
|
||||
return [
|
||||
center.x + r * Math.cos(d.angle),
|
||||
center.y + r * Math.sin(d.angle)
|
||||
];
|
||||
});
|
||||
|
||||
// Draw filled polygon (data area)
|
||||
svg.append("polygon")
|
||||
.attr("class", "area")
|
||||
.attr("points", valuePoints.map(p => p.join(",")).join(" "));
|
||||
|
||||
// OPTIONAL: Debug points at each value vertex
|
||||
svg.selectAll(".debug")
|
||||
.data(valuePoints)
|
||||
.join("circle")
|
||||
.attr("class", "debug")
|
||||
.attr("cx", d => d[0])
|
||||
.attr("cy", d => d[1])
|
||||
.attr("r", 3);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
111
eoq_code/draw3axis.js
Normal file
111
eoq_code/draw3axis.js
Normal file
@ -0,0 +1,111 @@
|
||||
|
||||
function drawGraph() {
|
||||
let ValA = 0.8;
|
||||
let ValB = 0.4;
|
||||
let ValC = 0.6;
|
||||
createSVGgraphic("#Q1","FeatureA", "FeatureB", "FeatureC", ValA, ValB, ValC);
|
||||
createSVGgraphic("#Q2","ConceptA", "ConceptB", "ConceptC", 0.2, 0.8, 0.8);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function createSVGgraphic(divID,FA,FB,FC,VA,VB,VC) {
|
||||
|
||||
const svg = d3.select(divID);
|
||||
const width = +svg.attr("width");
|
||||
const height = +svg.attr("height");
|
||||
const radius = 150;
|
||||
const center = { x: width / 2, y: height / 2 };
|
||||
|
||||
// Define axes with A at the top (rotate -90 degrees)
|
||||
const axes = [
|
||||
{ name: FA, angle: 0 },
|
||||
{ name: FB, angle: 120 },
|
||||
{ name: FC, angle: 240 }
|
||||
];
|
||||
axes.forEach(d => d.angle = (d.angle - 90) * Math.PI / 180);
|
||||
|
||||
// Sample data: [0-1] scale
|
||||
const values = [VA, VB, VC];
|
||||
|
||||
// Draw axis lines
|
||||
svg.append("g")
|
||||
.attr("class", "axis")
|
||||
.selectAll("line")
|
||||
.data(axes)
|
||||
.join("line")
|
||||
.attr("x1", center.x)
|
||||
.attr("y1", center.y)
|
||||
.attr("x2", d => center.x + radius * Math.cos(d.angle))
|
||||
.attr("y2", d => center.y + radius * Math.sin(d.angle));
|
||||
|
||||
// Draw grid rings (polygons)
|
||||
const levels = 5;
|
||||
for (let i = 1; i <= levels; i++) {
|
||||
const r = (radius / levels) * i;
|
||||
const points = axes.map(d => {
|
||||
return [
|
||||
center.x + r * Math.cos(d.angle),
|
||||
center.y + r * Math.sin(d.angle)
|
||||
];
|
||||
});
|
||||
svg.append("polygon")
|
||||
.attr("class", "grid")
|
||||
.attr("points", points.map(p => p.join(",")).join(" "));
|
||||
}
|
||||
|
||||
// Draw axis ticks
|
||||
const tickLength = 5;
|
||||
axes.forEach(axis => {
|
||||
for (let i = 1; i <= levels; i++) {
|
||||
const r = (radius / levels) * i;
|
||||
const angle = axis.angle;
|
||||
|
||||
const x1 = center.x + r * Math.cos(angle);
|
||||
const y1 = center.y + r * Math.sin(angle);
|
||||
|
||||
// Perpendicular tick
|
||||
const perpAngle = angle + Math.PI / 2;
|
||||
const x2 = x1 + tickLength * Math.cos(perpAngle);
|
||||
const y2 = y1 + tickLength * Math.sin(perpAngle);
|
||||
|
||||
svg.append("line")
|
||||
.attr("x1", x1)
|
||||
.attr("y1", y1)
|
||||
.attr("x2", x2)
|
||||
.attr("y2", y2)
|
||||
.attr("class", "tick");
|
||||
}
|
||||
});
|
||||
|
||||
// Convert values to coordinates
|
||||
const valuePoints = axes.map((d, i) => {
|
||||
const r = radius * values[i];
|
||||
return [
|
||||
center.x + r * Math.cos(d.angle),
|
||||
center.y + r * Math.sin(d.angle)
|
||||
];
|
||||
});
|
||||
|
||||
// Draw data area
|
||||
svg.append("polygon")
|
||||
.attr("class", "area")
|
||||
.attr("points", valuePoints.map(p => p.join(",")).join(" "))
|
||||
|
||||
// Add axis labels
|
||||
svg.selectAll(".label")
|
||||
.data(axes)
|
||||
.join("text")
|
||||
.attr("class", "label")
|
||||
.attr("x", d => center.x + (radius + 20) * Math.cos(d.angle))
|
||||
.attr("y", (d,i) => {
|
||||
if (i === 0) { //adjust for 1st access, rather than using d.name
|
||||
return center.y + (radius + 20) * Math.sin(d.angle)
|
||||
} else
|
||||
return center.y + (radius + 20 + 30) * Math.sin(d.angle)
|
||||
})
|
||||
.text(d => d.name);
|
||||
|
||||
|
||||
}
|
||||
|
285
eoq_code/drawbar.js
Normal file
285
eoq_code/drawbar.js
Normal file
@ -0,0 +1,285 @@
|
||||
|
||||
|
||||
|
||||
function drawBar(id,data) {
|
||||
//console.log(id);
|
||||
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", "6,2");
|
||||
|
||||
/*
|
||||
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("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) {
|
||||
return fetch('get_qid_counts.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ qid })
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not OK');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
// console.log(data);
|
||||
// console.log(data[0][Object.keys(data[0])[0]]);
|
||||
/* */
|
||||
bardata = {
|
||||
"-3": data[0][Object.keys(data[0])[0]],
|
||||
"-2": data[1][Object.keys(data[1])[0]],
|
||||
"-1": data[2][Object.keys(data[2])[0]],
|
||||
"0": data[3][Object.keys(data[3])[0]],
|
||||
"1": data[4][Object.keys(data[4])[0]],
|
||||
"2": data[5][Object.keys(data[5])[0]],
|
||||
"3": data[6][Object.keys(data[6])[0]],
|
||||
};
|
||||
|
||||
//console.log(bardata);
|
||||
drawBar(id,bardata);
|
||||
// return data; // Should be an array like [{ value: -3, count: 2 }, ..., { value: 3, count: 5 }]
|
||||
});
|
||||
}
|
178
eoq_code/drawtriangle.js
Normal file
178
eoq_code/drawtriangle.js
Normal file
@ -0,0 +1,178 @@
|
||||
|
||||
function drawtriangle(id,text,colour,values,rag,offset,mean)
|
||||
{
|
||||
|
||||
const svg = d3.select(`${id}`);
|
||||
const width = 500; // Fixed size. Was +svg.attr("width");
|
||||
const height = 500; // Fixed size. Was +svg.attr("height");
|
||||
const radius = 200;
|
||||
const center = { x: (width / 2) + offset.x, y: (height / 2) + offset.y};
|
||||
|
||||
let textcolour = colour;
|
||||
let polycolour = colour;
|
||||
let opacity = 0.3;
|
||||
|
||||
if (rag !== "")
|
||||
{
|
||||
polycolour = rag;
|
||||
textcolour = "black";
|
||||
console.log("rag: "+rag);
|
||||
opacity = 0.6;
|
||||
}
|
||||
|
||||
|
||||
// Define axes with A at the top (rotate -90 degrees)
|
||||
const axes = [
|
||||
{ name: "Feature A", angle: 0 },
|
||||
{ name: "Feature B", angle: 120 },
|
||||
{ name: "Feature C", angle: 240 }
|
||||
];
|
||||
axes.forEach(d => d.angle = (d.angle - 90) * Math.PI / 180);
|
||||
|
||||
// Sample data: [0-1] scale
|
||||
// const values = [0.8, 0.1, 0.76]; Now passed in function args
|
||||
|
||||
// Don't draw if class "no-axis"
|
||||
if (!svg.classed("no-axis")) {
|
||||
// Draw axis lines
|
||||
svg.append("g")
|
||||
.attr("class", "axis")
|
||||
.selectAll("line")
|
||||
.data(axes)
|
||||
.join("line")
|
||||
.attr("x1", center.x)
|
||||
.attr("y1", center.y)
|
||||
.attr("x2", d => center.x + radius * Math.cos(d.angle))
|
||||
.attr("y2", d => center.y + radius * Math.sin(d.angle));
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Draw grid rings (polygons)
|
||||
const levels = 7;
|
||||
for (let i = 1; i <= levels; i++) {
|
||||
const r = (radius / levels) * i;
|
||||
const points = axes.map(d => {
|
||||
return [
|
||||
center.x + r * Math.cos(d.angle),
|
||||
center.y + r * Math.sin(d.angle)
|
||||
];
|
||||
});
|
||||
svg.append("polygon")
|
||||
.attr("class", "grid")
|
||||
.attr("points", points.map(p => p.join(",")).join(" "));
|
||||
}
|
||||
|
||||
|
||||
// Draw axis ticks
|
||||
let tickLength = 5;
|
||||
if (svg.classed("no-axis")) {
|
||||
tickLength = 0;
|
||||
}
|
||||
axes.forEach(axis => {
|
||||
for (let i = 1; i <= levels -1 ; i++) { // -1 to remove tick outside outline
|
||||
const r = (radius / levels) * i;
|
||||
const angle = axis.angle;
|
||||
|
||||
const x1 = center.x + r * Math.cos(angle);
|
||||
const y1 = center.y + r * Math.sin(angle);
|
||||
|
||||
// Perpendicular tick
|
||||
const perpAngle = angle + Math.PI / 2;
|
||||
const x2 = x1 + tickLength * Math.cos(perpAngle);
|
||||
const y2 = y1 + tickLength * Math.sin(perpAngle);
|
||||
|
||||
svg.append("line")
|
||||
.attr("x1", x1)
|
||||
.attr("y1", y1)
|
||||
.attr("x2", x2)
|
||||
.attr("y2", y2)
|
||||
.attr("class", "tick");
|
||||
}
|
||||
});
|
||||
|
||||
// Convert values to coordinates
|
||||
const valuePoints = axes.map((d, i) => {
|
||||
const r = radius * values[i];
|
||||
return [
|
||||
center.x + r * Math.cos(d.angle),
|
||||
center.y + r * Math.sin(d.angle)
|
||||
];
|
||||
});
|
||||
|
||||
// Draw data area
|
||||
svg.append("polygon")
|
||||
.attr("class", "area")
|
||||
.attr("points", valuePoints.map(p => p.join(",")).join(" "))
|
||||
.attr("fill", polycolour) // #d76a23 with 30% opacity (translucent)
|
||||
.attr("fill-opacity", opacity)
|
||||
.attr("stroke", polycolour) // Outer line color
|
||||
.attr("stroke-width", 1); // Thin outer line
|
||||
|
||||
// Draw outer triangle border (goes to edge of chart)
|
||||
const outerPoints = axes.map(d => [
|
||||
center.x + radius * Math.cos(d.angle),
|
||||
center.y + radius * Math.sin(d.angle)
|
||||
]);
|
||||
|
||||
// Draw border separately (on top)
|
||||
svg.append("polygon")
|
||||
.attr("points", outerPoints.map(p => p.join(",")).join(" "))
|
||||
.attr("fill","none")
|
||||
.attr("stroke", colour) // Outer line color
|
||||
.attr("stroke-width", 5); // Thicker outer line
|
||||
|
||||
// Put label in bottom centre of triangle
|
||||
const bottomLeft = outerPoints[1];
|
||||
const bottomRight = outerPoints[2];
|
||||
|
||||
const midX = (bottomLeft[0] + bottomRight[0]) / 2;
|
||||
const midY = (bottomLeft[1] + bottomRight[1]) / 2;
|
||||
|
||||
//Don't draw the label for 'no-text' class
|
||||
if (!svg.classed("no-text")){
|
||||
svg.append("text")
|
||||
.attr("x", midX)
|
||||
.attr("y", midY - 10) // Alter for correct height
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", textcolour) /// My Colour
|
||||
.attr("font-size", "22px")
|
||||
.attr("font-weight", "bold")
|
||||
.text(`${text}`);
|
||||
}
|
||||
|
||||
// And now a central number
|
||||
let bigy = midY-45;
|
||||
let bigfont = "36px";
|
||||
if (svg.classed("big-number")){
|
||||
bigy = midY-85;
|
||||
bigfont = "70px";
|
||||
}
|
||||
svg.append("text")
|
||||
.attr("x", midX)
|
||||
.attr("y", bigy)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "black") // 🎨 customize color
|
||||
.attr("font-size", bigfont)
|
||||
.attr("font-weight", "bold")
|
||||
.attr("font-family", "Courier New, monospace")
|
||||
.text(`${mean}`);
|
||||
|
||||
// Add axis labels
|
||||
/*
|
||||
svg.selectAll(".label")
|
||||
.data(axes)
|
||||
.join("text")
|
||||
.attr("class", "label")
|
||||
.attr("x", d => center.x + (radius + 20) * Math.cos(d.angle))
|
||||
.attr("y", (d,i) => {
|
||||
if (i === 0) { //adjust for 1st access, rather than using d.name
|
||||
return center.y + (radius + 20) * Math.sin(d.angle)
|
||||
} else
|
||||
return center.y + (radius + 20 + 30) * Math.sin(d.angle)
|
||||
})
|
||||
.text(d => d.name);
|
||||
*/
|
||||
|
||||
|
||||
}
|
161
eoq_code/drawtriangleinverted.js
Normal file
161
eoq_code/drawtriangleinverted.js
Normal file
@ -0,0 +1,161 @@
|
||||
|
||||
function drawtriangleinverted(id,text,colour,values,rag,offset,mean)
|
||||
{
|
||||
|
||||
const svg = d3.select(`${id}`);
|
||||
const width = 500; // Fixed size. Was +svg.attr("width");
|
||||
const height = 500; // Fixed size. Was +svg.attr("height");
|
||||
const radius = 200;
|
||||
const center = { x: (width / 2) + offset.x, y: (height / 2) + offset.y};
|
||||
|
||||
let textcolour = colour;
|
||||
let polycolour = colour;
|
||||
let opacity = 0.3;
|
||||
|
||||
if (rag !== "")
|
||||
{
|
||||
polycolour = rag;
|
||||
textcolour = "black";
|
||||
console.log("rag: "+rag);
|
||||
opacity = 0.6;
|
||||
}
|
||||
|
||||
// Update angles for flat top triangle
|
||||
const axes = [
|
||||
{ name: "Feature A", angle: 60 }, // Left axis
|
||||
{ name: "Feature B", angle: 180 }, // Top axis (flat)
|
||||
{ name: "Feature C", angle: 300 } // Right axis
|
||||
];
|
||||
|
||||
// Convert angles to radians (subtract 90 degrees to rotate the whole chart)
|
||||
axes.forEach(d => d.angle = (d.angle - 90) * Math.PI / 180);
|
||||
|
||||
// Sample data: [0-1] scale
|
||||
// const values = [0.8, 0.1, 0.76]; Now passed in function args
|
||||
// Don't draw if class "no-axis"
|
||||
if (!svg.classed("no-axis")) {
|
||||
// Draw axis lines
|
||||
svg.append("g")
|
||||
.attr("class", "axis")
|
||||
.selectAll("line")
|
||||
.data(axes)
|
||||
.join("line")
|
||||
.attr("x1", center.x)
|
||||
.attr("y1", center.y)
|
||||
.attr("x2", d => center.x + radius * Math.cos(d.angle))
|
||||
.attr("y2", d => center.y + radius * Math.sin(d.angle));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Draw grid rings (polygons)
|
||||
const levels = 7;
|
||||
for (let i = 1; i <= levels; i++) {
|
||||
const r = (radius / levels) * i;
|
||||
const points = axes.map(d => {
|
||||
return [
|
||||
center.x + r * Math.cos(d.angle),
|
||||
center.y + r * Math.sin(d.angle)
|
||||
];
|
||||
});
|
||||
svg.append("polygon")
|
||||
.attr("class", "grid")
|
||||
.attr("points", points.map(p => p.join(",")).join(" "));
|
||||
}
|
||||
|
||||
|
||||
// Draw axis ticks
|
||||
let tickLength = 5;
|
||||
if (svg.classed("no-axis")) {
|
||||
tickLength = 0;
|
||||
}
|
||||
axes.forEach(axis => {
|
||||
for (let i = 1; i <= levels - 1; i++) { // -1 so that tick doesn't stick out
|
||||
const r = (radius / levels) * i;
|
||||
const angle = axis.angle;
|
||||
|
||||
const x1 = center.x + r * Math.cos(angle);
|
||||
const y1 = center.y + r * Math.sin(angle);
|
||||
|
||||
// Perpendicular tick
|
||||
const perpAngle = angle + Math.PI / 2;
|
||||
const x2 = x1 + tickLength * Math.cos(perpAngle);
|
||||
const y2 = y1 + tickLength * Math.sin(perpAngle);
|
||||
|
||||
svg.append("line")
|
||||
.attr("x1", x1)
|
||||
.attr("y1", y1)
|
||||
.attr("x2", x2)
|
||||
.attr("y2", y2)
|
||||
.attr("class", "tick");
|
||||
}
|
||||
});
|
||||
|
||||
// Convert values to coordinates
|
||||
const valuePoints = axes.map((d, i) => {
|
||||
const r = radius * values[i];
|
||||
return [
|
||||
center.x + r * Math.cos(d.angle),
|
||||
center.y + r * Math.sin(d.angle)
|
||||
];
|
||||
});
|
||||
|
||||
// Draw data area
|
||||
svg.append("polygon")
|
||||
.attr("class", "area")
|
||||
.attr("points", valuePoints.map(p => p.join(",")).join(" "))
|
||||
.attr("fill", polycolour) // #9f84b8
|
||||
.attr("fill-opacity", opacity) // with 30% opacity (translucent)
|
||||
.attr("stroke", polycolour) // Outer line color
|
||||
.attr("stroke-width", 1); // Thin outer line
|
||||
|
||||
// Draw outer triangle border (goes to edge of chart)
|
||||
const outerPoints = axes.map(d => [
|
||||
center.x + radius * Math.cos(d.angle),
|
||||
center.y + radius * Math.sin(d.angle)
|
||||
]);
|
||||
|
||||
// Draw border separately (on top)
|
||||
svg.append("polygon")
|
||||
.attr("points", outerPoints.map(p => p.join(",")).join(" "))
|
||||
.attr("fill","none")
|
||||
.attr("stroke", colour) // Outer line color
|
||||
.attr("stroke-width", 5); // Thicker outer line
|
||||
|
||||
// Put label in bottom centre of triangle (adjust for flat top)
|
||||
const bottomLeft = outerPoints[0];
|
||||
const bottomRight = outerPoints[2];
|
||||
|
||||
const midX = (bottomLeft[0] + bottomRight[0]) / 2;
|
||||
const midY = (bottomLeft[1] + bottomRight[1]) / 2;
|
||||
|
||||
//Don't draw the label for 'no-text' class
|
||||
if (!svg.classed("no-text")){
|
||||
svg.append("text")
|
||||
.attr("x", midX)
|
||||
.attr("y", midY + 25) // Adjust for correct height (moved to bottom)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", textcolour) // My Colour
|
||||
.attr("font-size", "22px")
|
||||
.attr("font-weight", "bold")
|
||||
.text(`${text}`);
|
||||
}
|
||||
|
||||
// And now a central number
|
||||
let bigy = midY + 70;
|
||||
let bigfont = "36px";
|
||||
if (svg.classed("big-number")){
|
||||
bigy = midY + 120;
|
||||
bigfont = "70px";
|
||||
}
|
||||
svg.append("text")
|
||||
.attr("x", midX)
|
||||
.attr("y", bigy)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "black") // 🎨 customize color
|
||||
.attr("font-size", bigfont)
|
||||
.attr("font-weight", "bold")
|
||||
.attr("font-family", "Courier New, monospace")
|
||||
.text(`${mean}`);
|
||||
|
||||
}
|
152
eoq_code/getData.php
Normal file
152
eoq_code/getData.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
// Settings
|
||||
$config = require 'config.php';
|
||||
require_once 'db.php'; // include the connection
|
||||
$apiToken = $config['api_token'];
|
||||
$dataCentre = $config['data_centre'];
|
||||
$surveyId = $_POST['survey_id'] ?? null;
|
||||
|
||||
if (!$surveyId) {
|
||||
$surveyId="SV_bD838sNKZEmi6Tc"; // *****FIXME***** JUST FOR DEVELOPMENT
|
||||
//die("No survey ID provided.");
|
||||
}
|
||||
|
||||
$baseUrl = "https://$dataCentre.qualtrics.com/API/v3";
|
||||
$headers = [
|
||||
"X-API-TOKEN: $apiToken",
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
// Step 1: Start the export (with compress = false)
|
||||
$exportUrl = "$baseUrl/surveys/$surveyId/export-responses/";
|
||||
$postFields = json_encode([
|
||||
"format" => "json",
|
||||
"compress" => false
|
||||
]);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $exportUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
echo "cURL Error: " . curl_error($ch);
|
||||
curl_close($ch);
|
||||
exit;
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
echo "<pre>".$surveyId." ".$exportUrl."</pre>";
|
||||
if (!isset($result['result']['progressId'])) {
|
||||
|
||||
echo "<pre>Export API Response:\n";
|
||||
print_r($response);
|
||||
echo "</pre>";
|
||||
die("Failed to start export.\n$response");
|
||||
}
|
||||
$progressId = $result['result']['progressId'];
|
||||
|
||||
echo "Polling for completion";
|
||||
|
||||
|
||||
|
||||
// Step 2: Poll for completion
|
||||
$progressUrl = "$exportUrl/$progressId";
|
||||
do {
|
||||
sleep(2); // avoid hammering the API
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $progressUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
$status = $result['result']['status'] ?? 'failed';
|
||||
} while ($status !== 'complete' && $status !== 'failed');
|
||||
|
||||
if ($status !== 'complete') {
|
||||
die("Export failed or timed out.\n$response");
|
||||
}
|
||||
|
||||
$fileId = $result['result']['fileId'];
|
||||
|
||||
// Step 4: Download the file (uncompressed JSON)
|
||||
$downloadUrl = "$exportUrl/$fileId/file";
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $downloadUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
die("Error downloading the file: " . curl_error($ch));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// Decode the response
|
||||
$data = json_decode($response, true);
|
||||
|
||||
//Save surveyId
|
||||
//insertSurveyIfNotExists($identifier, $description); // The identifier is actually the surveyId, and the description isn't included at this point.
|
||||
$surveyIndex = getOrCreateSurvey($surveyId, "");
|
||||
|
||||
echo "-------".$surveyIndex."-----------";
|
||||
|
||||
// $data = $result['result'][''] ?? [];
|
||||
echo "Total responses: " . count($data['responses']) . "\n\n<br>";
|
||||
|
||||
if (isset($data['responses']) && is_array($data['responses'])) {
|
||||
foreach ($data['responses'] as $response) {
|
||||
echo "Response ID: " . $response['responseId'] . "\n";
|
||||
//Insert responseId into the database
|
||||
//
|
||||
$startDate = new DateTime($response['values']['startDate']);
|
||||
$startDateFormatted = $startDate->format('Y-m-d H:i:s');
|
||||
$endDate = new DateTime($response['values']['endDate']);
|
||||
$endDateFormatted = $endDate->format('Y-m-d H:i:s');
|
||||
$recordedDate = new DateTime($response['values']['recordedDate']);
|
||||
$recordedDateFormatted = $recordedDate->format('Y-m-d H:i:s');
|
||||
|
||||
$responseData = [
|
||||
'responseId' => $response['responseId'],
|
||||
'surveyId' => $surveyIndex,
|
||||
'startDate' => $startDateFormatted,
|
||||
'endDate' => $endDateFormatted,
|
||||
'status' => $response['values']['status'],
|
||||
'ipAddress' => $response['values']['ipAddress'],
|
||||
'progress' => $response['values']['progress'],
|
||||
'duration' => $response['values']['duration'],
|
||||
'finished' => $response['values']['finished'],
|
||||
'recordedDate' => $recordedDateFormatted,
|
||||
'locationLatitude' => $response['values']['locationLatitude'],
|
||||
'locationLongitude' => $response['values']['locationLongitude'],
|
||||
'distributionChannel' => $response['values']['distributionChannel'],
|
||||
'userLanguage' => $response['values']['userLanguage']
|
||||
];
|
||||
|
||||
|
||||
$responseIndex = upsertResponse($responseData);
|
||||
// Now read in the answers. These all start with QID????? etc
|
||||
$answers = $response['values'];
|
||||
|
||||
insertAnswers($pdo, $surveyIndex, $responseIndex, $answers);
|
||||
|
||||
// Prepare and execute the INSERT statement
|
||||
//
|
||||
//
|
||||
}
|
||||
} else {
|
||||
echo "No responses found.";
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
||||
|
66
eoq_code/getQuestions.php
Normal file
66
eoq_code/getQuestions.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
// Load config
|
||||
$config = require 'config.php';
|
||||
$apiToken = $config['api_token'];
|
||||
$dataCenter = $config['data_centre'];
|
||||
|
||||
// Get the survey ID from POST
|
||||
$surveyId = $_POST['survey_id'] ?? null;
|
||||
if (!$surveyId) {
|
||||
//$surveyId="SV_cAstEvm4ZrPaqGi";
|
||||
$surveyId="SV_cwKjMqAqGxImjMG";
|
||||
#die("No survey ID provided.");
|
||||
}
|
||||
|
||||
// Build URL
|
||||
$baseUrl = "https://$dataCenter.qualtrics.com/API/v3";
|
||||
$questionsUrl = "$baseUrl/survey-definitions/$surveyId/questions";
|
||||
|
||||
// Set headers
|
||||
$headers = [
|
||||
"X-API-TOKEN: $apiToken",
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
// Make request
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $questionsUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
die("Request error: " . curl_error($ch));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// Output the raw JSON
|
||||
//header('Content-Type: application/json');
|
||||
//echo $response;
|
||||
//var_dump($response);
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$questions = $data['result']['elements'] ?? [];
|
||||
echo '<pre>';
|
||||
print_r(array_slice($questions, 0, 1)); // Show one question object
|
||||
echo '</pre>';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Survey Questions</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Survey Questions</h1>
|
||||
|
||||
<ul>
|
||||
<?php foreach ($questions as $qid => $question): ?>
|
||||
<li>
|
||||
<strong><?= htmlspecialchars($qid) ?>:</strong>
|
||||
<?= htmlspecialchars($question['QuestionText'] ?? '[No question text]') ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
68
eoq_code/getSurveys.php
Normal file
68
eoq_code/getSurveys.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
// Set your API key and data center
|
||||
$apiToken = 'GZjoFiLmb2j62s8AHmMWKN25BZjGBhsU5ez4cwjn';
|
||||
$dataCenter = 'fra1'; // Your data center, e.g., 'fra1'
|
||||
|
||||
// Set the endpoint URL
|
||||
$baseUrl = "https://$dataCenter.qualtrics.com/API/v3/surveys";
|
||||
|
||||
// Initialize cURL session
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $baseUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"X-API-TOKEN: $apiToken",
|
||||
"Content-Type: application/json"
|
||||
]);
|
||||
|
||||
// Execute the request
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// Check for cURL errors
|
||||
if(curl_errno($ch)){
|
||||
die('Request Error: ' . curl_error($ch));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// Decode the response
|
||||
$result = json_decode($response, true);
|
||||
$surveys = $result['result']['elements'] ?? [];
|
||||
|
||||
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Select a Qualtrics Survey</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Select a Survey</h1>
|
||||
|
||||
<?php if (empty($surveys)): ?>
|
||||
<p>No surveys found or an error occurred.</p><br><br><hr><br>
|
||||
|
||||
<pre><?php echo print_r($result); ?></pre>
|
||||
<pre><?php
|
||||
|
||||
|
||||
?></pre>
|
||||
<?php else: ?>
|
||||
<form method="POST" action="getData.php">
|
||||
<label for="survey_id">Choose a survey to download data and produce reports:</label>
|
||||
<select name="survey_id" id="survey_id">
|
||||
<?php foreach ($surveys as $survey): ?>
|
||||
<option value="<?= htmlspecialchars($survey['id']) ?>">
|
||||
<?= htmlspecialchars($survey['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<br><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
67
eoq_code/get_qid_counts.html
Normal file
67
eoq_code/get_qid_counts.html
Normal file
@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>QID Value Counts</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 2em; }
|
||||
label, input, button { font-size: 1em; margin: 0.5em 0; }
|
||||
ul { list-style-type: none; padding-left: 0; }
|
||||
li { padding: 0.3em 0; }
|
||||
.error { color: red; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Query Value Counts by QID</h1>
|
||||
|
||||
<form id="qidForm">
|
||||
<label for="qidInput">Enter QID:</label><br />
|
||||
<input type="text" id="qidInput" name="qid" required />
|
||||
<button type="submit">Get Counts</button>
|
||||
</form>
|
||||
|
||||
<div id="results"></div>
|
||||
|
||||
<script>
|
||||
document.getElementById('qidForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const qid = document.getElementById('qidInput').value.trim();
|
||||
const resultsDiv = document.getElementById('results');
|
||||
resultsDiv.innerHTML = 'Loading...';
|
||||
|
||||
fetch('get_qid_counts.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({qid})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
resultsDiv.innerHTML = `<p class="error">${data.error}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
resultsDiv.innerHTML = '<p>No data found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<h2>Value Counts for ' + qid + '</h2><ul>';
|
||||
data.forEach(item => {
|
||||
html += `<li><strong>Value ${item.value}:</strong> ${item.count}</li>`;
|
||||
});
|
||||
html += '</ul>';
|
||||
|
||||
resultsDiv.innerHTML = html;
|
||||
})
|
||||
.catch(err => {
|
||||
resultsDiv.innerHTML = `<p class="error">Error: ${err.message}</p>`;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
67
eoq_code/get_qid_counts.php
Normal file
67
eoq_code/get_qid_counts.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Basic input validation and sanitization
|
||||
if (!isset($_POST['qid']) || empty($_POST['qid'])) {
|
||||
echo json_encode(['error' => 'Missing QID parameter']);
|
||||
exit;
|
||||
}
|
||||
$qid = $_POST['qid'];
|
||||
|
||||
// Database connection (adjust credentials accordingly)
|
||||
//
|
||||
$config = require 'config.php';
|
||||
$host = $config['db_host'];
|
||||
$db = $config['db_name'];
|
||||
$user = $config['db_user'];
|
||||
$pass = $config['db_pass'];
|
||||
$charset = 'utf8mb4';
|
||||
|
||||
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
$sql = "
|
||||
SELECT value, COUNT(*) AS count
|
||||
FROM Answers
|
||||
WHERE QID = :qid
|
||||
AND value BETWEEN -3 AND 3
|
||||
GROUP BY value
|
||||
ORDER BY value
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute(['qid' => $qid]);
|
||||
$results = $stmt->fetchAll();
|
||||
|
||||
// Initialize array with all possible values from -3 to 3 with zero counts
|
||||
$counts = [];
|
||||
for ($i = -3; $i <= 3; $i++) {
|
||||
$counts[$i] = 0;
|
||||
}
|
||||
|
||||
// Fill in counts from query results
|
||||
foreach ($results as $row) {
|
||||
$counts[(int)$row['value']] = (int)$row['count'];
|
||||
}
|
||||
|
||||
// Return as JSON array of objects [{value: -3, count: 5}, ...]
|
||||
$response = [];
|
||||
foreach ($counts as $value => $count) {
|
||||
//$response[] = ['value' => $value, 'count' => $count];
|
||||
$response[] = ['"'.strval($value).'"' => $count];
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
337
eoq_code/populateGraphics.js
Normal file
337
eoq_code/populateGraphics.js
Normal file
@ -0,0 +1,337 @@
|
||||
function RAGGED(average){
|
||||
// This returns the correct colour red, amber or gree depending on the value presented
|
||||
if (average <= -1) {
|
||||
return "red";
|
||||
}
|
||||
if (average >= 1) {
|
||||
return "green";
|
||||
}
|
||||
return "#ffbf00";
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getAverage(prefix) {
|
||||
console.log("Prefix:", prefix);
|
||||
|
||||
const elements = Array.from(document.querySelectorAll(`svg[id^="${prefix}"] tspan.average`));
|
||||
|
||||
const numbers = elements.map(el => {
|
||||
const text = el.textContent;
|
||||
//console.log("Raw text content:", text);
|
||||
const value = parseFloat(text);
|
||||
//console.log("Parsed value:", value);
|
||||
return value;
|
||||
}).filter(n => !isNaN(n));
|
||||
|
||||
//console.log("Parsed numeric values:", numbers);
|
||||
|
||||
if (numbers.length === 0) {
|
||||
console.warn("No valid numbers found!");
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const avg = numbers.reduce((sum, n) => sum + n, 0) / numbers.length;
|
||||
//console.log("Average of averages:", avg);
|
||||
return avg;
|
||||
}
|
||||
|
||||
|
||||
function loaded(){
|
||||
console.log("loaded");
|
||||
|
||||
let amber = '#ffbf00';
|
||||
//doBigWhiteTriangle('svg1');
|
||||
//drawtriangle('#svg1','Roles','#008845',[0.5,0.6,0.5],'red', { x: 0, y: 350 },-0.7);
|
||||
//drawtriangle('#svg1','Actions','#b2c8c4',[0.1,0.6,0.5],'green',{ x: 370, y: 350 },2.3);
|
||||
//drawtriangle('#svg1','Approach','#ed4c0c',[0.3,0.6,0.5],amber,{ x: 185, y: 30 },1.4);
|
||||
//drawtriangleinverted('#svg1','Impact','#74469c',[0.7,0.6,0.8],'green',{ x: 185, y: 243},2.4);
|
||||
//makeSvgRightClickable('svg1');
|
||||
|
||||
|
||||
|
||||
//doLittleWhiteTriangle('svg2');
|
||||
//drawtriangle('#svg2','Roles','#008845',[0.5,0.6,0.5],'', { x: 0, y: 100 },-0.7);
|
||||
|
||||
//EO ROLES
|
||||
//Great Employee Owners
|
||||
let average = doBarData('#svg1_1','QID35_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg1_2','QID35_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg1_3','QID35_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg1_4','QID35_9');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
doBarData('#svg1_5','QID35_10');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
|
||||
|
||||
|
||||
//Great EO Leaders
|
||||
average = doBarData('#svg2_1','QID36_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg2_2','QID36_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg2_3','QID36_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg2_4','QID36_9');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
|
||||
|
||||
//Great EO Governance
|
||||
average = doBarData('#svg3_1','QID37_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg3_2','QID37_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg3_3','QID37_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg3_4','QID37_20');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
doBarData('#svg3_5','QID37_21');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
doBarData('#svg3_6','QID37_22');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
|
||||
|
||||
//EO APPROACH
|
||||
//Great EO Culture
|
||||
average = doBarData('#svg4_1','QID2_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg4_2','QID2_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg4_3','QID2_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg4_4','QID2_9');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
|
||||
|
||||
//Great EO Engagement
|
||||
average = doBarData('#svg5_1','QID33_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg5_2','QID33_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg5_3','QID33_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg5_4','QID33_9');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
|
||||
|
||||
//Great EO Stewardship
|
||||
average = doBarData('#svg6_1','QID34_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg6_2','QID34_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg6_3','QID34_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg6_4','QID34_17');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
|
||||
|
||||
//EO ACTIONS
|
||||
//Great EO Strategy
|
||||
average = doBarData('#svg7_1','QID40_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg7_2','QID40_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg7_3','QID40_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg7_4','QID40_9');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
|
||||
|
||||
//Great EO Innovation
|
||||
average = doBarData('#svg8_1','QID41_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg8_2','QID41_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg8_3','QID41_19');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg8_4','QID41_20');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
doBarData('#svg8_5','QID41_21');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
|
||||
|
||||
//Great EO Advantage
|
||||
average = doBarData('#svg9_1','QID42_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg9_2','QID42_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg9_3','QID42_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
|
||||
|
||||
//EO RESULTS
|
||||
//Great EO Measurement
|
||||
average = doBarData('#svg10_1','QID44_21');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg10_2','QID44_7');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg10_3','QID44_2');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg10_4','QID44_8');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
|
||||
|
||||
//Great EO Evaluation
|
||||
average = doBarData('#svg11_1','QID45_24');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg11_2','QID45_25');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg11_3','QID45_26');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg11_4','QID45_29');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
|
||||
|
||||
//Great EO Impact
|
||||
average = doBarData('#svg12_1','QID46_27');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg12_2','QID46_28');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg12_3','QID46_29');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg12_4','QID46_30');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
doBarData('#svg12_5','QID46_31');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
doBarData('#svg12_6','QID46_32');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
doBarData('#svg12_7','QID46_33');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg12_8','QID46_34');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
doBarData('#svg12_9','QID46_36');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
doBarData('#svg12_10','QID46_37');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Make sure that evreything is loaded!
|
||||
setTimeout(() => {
|
||||
console.log("Waited 2 seconds");
|
||||
// We now have access to all of the averages, so can calculate the basic triangle.
|
||||
|
||||
//EO ROLES
|
||||
let svg1_avg = (getAverage("svg1_")+3)/7;
|
||||
let svg2_avg = (getAverage("svg2_")+3)/7;
|
||||
let svg3_avg = (getAverage("svg3_")+3)/7;
|
||||
let roles_avg = (7*(svg1_avg+svg2_avg+svg3_avg)/3)-3; // this converts the scales lengths to the correct value
|
||||
|
||||
console.log(svg1_avg,svg2_avg,svg3_avg, roles_avg);
|
||||
|
||||
doLittleWhiteTriangle('svg_roles');
|
||||
//drawtriangle('#svg_roles','Roles','#008845',[0.25,0.5,0.75],'', { x: 0, y: 100 },roles_avg.toFixed(1));
|
||||
drawtriangle('#svg_roles','Roles','#008845',[svg1_avg,svg2_avg,svg3_avg],'', { x: 0, y: 100 },roles_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_roles');
|
||||
|
||||
doLittleWhiteTriangle('svg_roles_RAG');
|
||||
drawtriangle('#svg_roles_RAG','Roles','#008845',[svg1_avg,svg2_avg,svg3_avg],RAGGED(roles_avg), { x: 0, y: 100 },roles_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_roles_RAG');
|
||||
|
||||
doLittleWhiteTriangle('svg_roles_test');
|
||||
drawtriangle('#svg_roles_test','Roles','#008845',[0.95,0.95,0.95],RAGGED(roles_avg), { x: 0, y: 100 },roles_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_roles_test');
|
||||
|
||||
|
||||
//EO APPROACH
|
||||
let svg4_avg = (getAverage("svg4_")+3)/7;
|
||||
let svg5_avg = (getAverage("svg5_")+3)/7;
|
||||
let svg6_avg = (getAverage("svg6_")+3)/7;
|
||||
let approach_avg = (7*(svg4_avg+svg5_avg+svg6_avg)/3)-3; // this converts the scales lengths to the correct value
|
||||
|
||||
console.log(svg4_avg,svg5_avg,svg6_avg,approach_avg);
|
||||
|
||||
doLittleWhiteTriangle('svg_approach');
|
||||
//drawtriangle('#svg_approach','Approach','#008845',[0.25,0.5,0.75],'', { x: 0, y: 100 },average_avg.toFixed(1));
|
||||
drawtriangle('#svg_approach','Approach','#ed4c0c',[svg4_avg,svg5_avg,svg6_avg],'', { x: 0, y: 100 },approach_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_approach');
|
||||
|
||||
doLittleWhiteTriangle('svg_approach_RAG');
|
||||
drawtriangle('#svg_approach_RAG','Approach','#ed4c0c',[svg4_avg,svg5_avg,svg6_avg],RAGGED(approach_avg), { x: 0, y: 100 },approach_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_approach_RAG');
|
||||
|
||||
//doLittleWhiteTriangle('svg_approach_test');
|
||||
//drawtriangle('#svg_approach_test','Approach','#ed4c0c',[0.95,0.95,0.95],RAGGED(approach_avg), { x: 0, y: 100 },approach_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_approach_test');
|
||||
|
||||
|
||||
//EO ACTIONS
|
||||
let svg7_avg = (getAverage("svg7_")+3)/7;
|
||||
let svg8_avg = (getAverage("svg8_")+3)/7;
|
||||
let svg9_avg = (getAverage("svg9_")+3)/7;
|
||||
let actions_avg = (7*(svg7_avg+svg8_avg+svg9_avg)/3)-3; // this converts the scales lengths to the correct value
|
||||
|
||||
console.log(svg7_avg,svg8_avg,svg9_avg,actions_avg);
|
||||
|
||||
doLittleWhiteTriangle('svg_actions');
|
||||
//drawtriangle('#svg_actions','Actions','#b2c8c4',[0.25,0.5,0.75],'', { x: 0, y: 100 },actions_avg.toFixed(1));
|
||||
drawtriangle('#svg_actions','Actions','#b2c8c4',[svg7_avg,svg8_avg,svg9_avg],'', { x: 0, y: 100 },actions_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_actions');
|
||||
|
||||
doLittleWhiteTriangle('svg_actions_RAG');
|
||||
drawtriangle('#svg_actions_RAG','Actions','#b2c8c4',[svg7_avg,svg8_avg,svg9_avg],RAGGED(actions_avg), { x: 0, y: 100 },actions_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_actions_RAG');
|
||||
|
||||
//doLittleWhiteTriangle('svg_actions_test');
|
||||
//drawtriangle('#svg_actions_test','Actions','#b2c8c4',[0.95,0.95,0.95],RAGGED(actions_avg), { x: 0, y: 100 },actions_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_actions_test');
|
||||
|
||||
|
||||
//EO IMPACT
|
||||
let svg10_avg = (getAverage("svg10_")+3)/7;
|
||||
let svg11_avg = (getAverage("svg11_")+3)/7;
|
||||
let svg12_avg = (getAverage("svg12_")+3)/7;
|
||||
let results_avg = (7*(svg10_avg+svg11_avg+svg12_avg)/3)-3; // this converts the scales lengths to the correct value
|
||||
|
||||
console.log(svg10_avg,svg11_avg,svg12_avg,results_avg);
|
||||
|
||||
doLittleWhiteTriangle('svg_results');
|
||||
//drawtriangle('#svg_results','Results','#74469',[0.25,0.5,0.75],'', { x: 0, y: 100 },impact_avg.toFixed(1));
|
||||
drawtriangleinverted('#svg_results','Results','#74469c',[svg10_avg,svg11_avg,svg12_avg],'', { x: 0, y: 0 },results_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_results');
|
||||
|
||||
doLittleWhiteTriangle('svg_results_RAG');
|
||||
drawtriangleinverted('#svg_results_RAG','Results','#74469c',[svg10_avg,svg11_avg,svg12_avg],RAGGED(results_avg), { x: 0, y: 0 },results_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_results_RAG');
|
||||
|
||||
doLittleWhiteTriangle('svg_results_test');
|
||||
drawtriangleinverted('#svg_results_test','Results','#74469c',[0.95,0.95,0.95],RAGGED(results_avg), { x: 0, y: 0 },results_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_results_test');
|
||||
|
||||
|
||||
|
||||
doBigWhiteTriangle('svg_pyramid');
|
||||
drawtriangle('#svg_pyramid','Roles','#008845',[svg1_avg,svg2_avg,svg3_avg],'', { x: 0, y: 350 },roles_avg.toFixed(1));
|
||||
drawtriangle('#svg_pyramid','Actions','#b2c8c4',[svg4_avg,svg5_avg,svg6_avg],'',{ x: 370, y: 350 },actions_avg.toFixed(1));
|
||||
drawtriangle('#svg_pyramid','Approach','#ed4c0c',[svg7_avg,svg8_avg,svg9_avg],'',{ x: 185, y: 30 },approach_avg.toFixed(1));
|
||||
drawtriangleinverted('#svg_pyramid','Results','#74469c',[svg10_avg,svg11_avg,svg12_avg],'',{ x: 185, y: 243},results_avg.toFixed(1));
|
||||
makeSvgRightClickable('svg_pyramid');
|
||||
|
||||
|
||||
|
||||
doBigWhiteTriangle('svg_pyramid_RAG');
|
||||
drawtriangle('#svg_pyramid_RAG','Roles','#008845',[svg1_avg,svg2_avg,svg3_avg],RAGGED(roles_avg), { x: 0, y: 350 },roles_avg.toFixed(1));
|
||||
drawtriangle('#svg_pyramid_RAG','Actions','#b2c8c4',[svg4_avg,svg5_avg,svg6_avg],RAGGED(actions_avg),{ x: 370, y: 350 },actions_avg.toFixed(1));
|
||||
drawtriangle('#svg_pyramid_RAG','Approach','#ed4c0c',[svg7_avg,svg8_avg,svg9_avg],RAGGED(approach_avg),{ x: 185, y: 30 },approach_avg.toFixed(1));
|
||||
drawtriangleinverted('#svg_pyramid_RAG','Results','#74469c',[svg10_avg,svg11_avg,svg12_avg],RAGGED(results_avg),{ x: 185, y: 243},results_avg.toFixed(1));
|
||||
makeSvgRightClickable('svg_pyramid_RAG');
|
||||
|
||||
|
||||
doBigWhiteTriangle('svg_pyramid_RAG_test');
|
||||
drawtriangle('#svg_pyramid_RAG_test','Roles','#008845',[0.95,0.95,0.95],RAGGED(roles_avg), { x: 0, y: 350 },roles_avg.toFixed(1));
|
||||
drawtriangle('#svg_pyramid_RAG_test','Actions','#b2c8c4',[0.95,0.95,0.95],RAGGED(actions_avg),{ x: 370, y: 350 },actions_avg.toFixed(1));
|
||||
drawtriangle('#svg_pyramid_RAG_test','Approach','#ed4c0c',[0.95,0.95,0.95],RAGGED(approach_avg),{ x: 185, y: 30 },approach_avg.toFixed(1));
|
||||
drawtriangleinverted('#svg_pyramid_RAG_test','Results','#74469c',[0.95,0.95,0.95],RAGGED(results_avg),{ x: 185, y: 243},results_avg.toFixed(1));
|
||||
makeSvgRightClickable('svg_pyramid_RAG_test');
|
||||
|
||||
|
||||
}, 500);
|
||||
|
||||
|
||||
|
||||
}
|
347
eoq_code/reportTemplate.html
Normal file
347
eoq_code/reportTemplate.html
Normal file
@ -0,0 +1,347 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>EOQ Output</title>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<script src="./drawtriangle.js"></script>
|
||||
<script src="./drawtriangleinverted.js"></script>
|
||||
<script src="./drawbar.js"></script>
|
||||
<script src="./savesvg.js"></script>
|
||||
<script src="./populateGraphics.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
svg {
|
||||
background: none;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.axis line {
|
||||
stroke: black;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
line.tick {
|
||||
stroke: black;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
polygon.grid {
|
||||
fill: none;
|
||||
stroke: rgba(200, 200, 200, 0.5); /* light grey with 50% opacity */
|
||||
stroke-dasharray: 2,2;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-anchor: middle;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="loaded()">
|
||||
|
||||
|
||||
<h1>TITLE</h1>
|
||||
|
||||
<p>Dates: TO </p>
|
||||
|
||||
<p>Number of completed surveys: </p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h2>A Health Check for your Employee Owned Business</h2>
|
||||
|
||||
<p>Employee ownership (EO) has the potential to deliver outstanding benefits to individual owners and drive exceptional company performance.</p>
|
||||
|
||||
<p>However, research shows, for that to happen, a business must have excellent EO management practices in place.</p>
|
||||
|
||||
<p>To help identify EO best practice, we developed the EO Framework. It is built on insights gathered directly from leaders, senior decision-makers and employee owners on how they have successfully implemented impactful EO.</p>
|
||||
|
||||
<p>Developed by Great EO's founding partners in collaboration with the Employee Ownership Association, the EO Framework sets out four simple areas of EO management best practice:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>EO Roles:</strong> Clearly defined EO roles through which leaders, trustees and employee owners all understand the part they play in making EO successful.</li>
|
||||
<li><strong>EO Approach:</strong> Strong EO core practices that empower employees to contribute to success.</li>
|
||||
<li><strong>EO Actions:</strong> Specific areas of EO management practice that can harness the full potential of employee ownership to strengthen strategy, innovation, and brand difference.</li>
|
||||
<li><strong>EO Results:</strong> Structured and consistent ways of measuring EO practice to track and enhance owner benefits and business outcomes.</li>
|
||||
</ul>
|
||||
|
||||
<p>To bring the EO Framework to life, we have created a simple health check - the eoQ™ test - to help you assess the strength of your current EO practice and signpost areas for possible improvement. It should take up to 20-30 minutes to complete the questionnaire.</p>
|
||||
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_pyramid" width="1000" height="1000"></svg>
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_pyramid_RAG" width="1000" height="1000"></svg>
|
||||
<svg class="no-axis no-text big-number" style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_pyramid_RAG_test" width="1000" height="1000"></svg>
|
||||
<hr>
|
||||
|
||||
|
||||
<h2>1. EO Roles</h2>
|
||||
|
||||
<p>It is vital that individual leaders, trustees, employee representatives and all employee owners understand their roles to ensure a successful EO business.</p>
|
||||
|
||||
<p><strong>To what extent does everyone fulfil their responsibilities in your business?</strong></p>
|
||||
|
||||
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_roles" width="500" height="500"></svg>
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_roles_RAG" width="500" height="500"></svg>
|
||||
<svg class = "no-axis no-text big-number" style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_roles_test" width="500" height="500"></svg>
|
||||
|
||||
<hr>
|
||||
<h3>Q14 Great Employee Owners</h3>
|
||||
|
||||
<p>1. Our employee owners understand how to think and act like an owner.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_1" width="800" height="200"></svg>
|
||||
<p>2. Our employee owners are committed to making the business stronger.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_2" width="800" height="200"></svg>
|
||||
<p>3. Our employee owners understand how their actions impact the current year’s business results.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_3" width="800" height="200"></svg>
|
||||
<p>4. Our employee owners understand how their actions help to execute our longer-term strategy.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_4" width="800" height="200"></svg>
|
||||
<p>5. Our employee owners understand how their actions strengthen our organisational culture.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_5" width="800" height="200"></svg>
|
||||
<p>Q15 Please add any additional comments:</p>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q16 Great EO Leaders</h3>
|
||||
|
||||
<p>1. Our executive leaders (those responsible for leading the business) are committed to realising the full business potential of our employee ownership.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg2_1" width="800" height="200"></svg>
|
||||
<p>2. Our executive leaders regularly engage our people to contribute as owners to our vision and strategy.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg2_2" width="800" height="200"></svg>
|
||||
<p>3. Our executive leaders ensure our people feel connected to our purpose, values, owner mindset and behaviours.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg2_3" width="800" height="200"></svg>
|
||||
<p>4. Our executive leaders are role models for our desired owner behaviours and culture.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg2_4" width="800" height="200"></svg>
|
||||
|
||||
<p>Q17 Please add any additional comments:</p>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q18 Great EO Governance</h3>
|
||||
|
||||
<p>1. All our governance bodies (boards, councils and voice groups) are clear on their role in ensuring long-term success for the business and its employee owners.</p>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_1" width="800" height="200"></svg>
|
||||
<p>2. All our governance bodies work collaboratively and effectively to promote an ownership culture.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_2" width="800" height="200"></svg>
|
||||
<p>3. All our governance bodies involve independent members who bring relevant experience, perspectives, and insights.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_3" width="800" height="200"></svg>
|
||||
<p>4. All our governance bodies effectively use the diverse skills, experiences and opinions of their members.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_4" width="800" height="200"></svg>
|
||||
<p>5. All our governance bodies fairly distribute ownership benefits in a manner that supports an ownership culture.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_5" width="800" height="200"></svg>
|
||||
<p>6. All our governance bodies make decisions in the long-term best interests of the organisation and its stakeholders.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_6" width="800" height="200"></svg>
|
||||
|
||||
<p>Q19 Please add any additional comments:</p>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h2>2. EO Approach</h2>
|
||||
<p>EO core practices build a strong ownership culture which drives engagement levels and encourages owner behaviours, including how to promote and protect the business over the long-term.</p>
|
||||
<p><strong>To what extent are you effectively engaging your people as owners and building an ownership mindset?</strong></p>
|
||||
|
||||
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_approach" width="500" height="500"></svg>
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_approach_RAG" width="500" height="500"></svg>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Great EO Culture</h3>
|
||||
|
||||
<p>1. Our behaviours as owners are improving the performance of the business.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg4_1" width="800" height="200"></svg>
|
||||
<p>2. Our people processes (recruitment, development, appraisal and reward) encourage and reward our desired owner behaviours.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg4_2" width="800" height="200"></svg>
|
||||
<p>3. Our culture creates a safe environment for feedback, learning, growth, health, well-being, inclusion and diversity.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg4_3" width="800" height="200"></svg>
|
||||
<p>4. Our culture and support for people develops and retains the right talent.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg4_4" width="800" height="200"></svg>
|
||||
|
||||
<p>Q22 Please add any additional comments:</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q23 Great EO Engagement</h3>
|
||||
|
||||
<p>1. All of our managers (those responsible for managing people and processes) help ensure our people understand how and where decisions are made in the business.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg5_1" width="800" height="200"></svg>
|
||||
<p>2. All of our managers help ensure our planning process, goal setting, and feedback channels enable people to influence decision-making as owners.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg5_2" width="800" height="200"></svg>
|
||||
<p>3. All of our managers help ensure our people clearly understand the connection between their owner behaviours and strategy, business performance and reward.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg5_3" width="800" height="200"></svg>
|
||||
<p>4. All of our managers help ensure our people are encouraged and empowered as owners to take responsibility for their own performance and contribution.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg5_4" width="800" height="200"></svg>
|
||||
|
||||
<p>Q24 Please add any additional comments:</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q25 Great EO Stewardship</h3>
|
||||
|
||||
<p>1. During tough times, we balance the current and future needs of our employee owners with those of the organisation.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg6_1" width="800" height="200"></svg>
|
||||
<p>2. Our employee owners identify and manage risks to protect our organisation and reputation.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg6_2" width="800" height="200"></svg>
|
||||
<p>3. We learn from tough times to become stronger in the future.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg6_3" width="800" height="200"></svg>
|
||||
<p>4. Our culture encourages leaders, managers and specialists to develop candidates for their own succession.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg6_4" width="800" height="200"></svg>
|
||||
|
||||
<p>Q26 Please add any additional comments:</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h2>3. EO Actions</h2>
|
||||
<p>An EO business can use its ownership model to provide commercial advantages as it faces into the world.</p>
|
||||
<p><strong>To what extent are you harnessing the contributions of employees as owners to strengthen the quality and effectiveness of your strategy, innovation and commercial sustainability?</strong></p>
|
||||
|
||||
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_actions" width="500" height="500"></svg>
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_actions_RAG" width="500" height="500"></svg>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q28 Great EO Strategy</h3>
|
||||
|
||||
<p>1. Our vision sets out the compelling benefits of our ownership model.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg7_1" width="800" height="200"></svg>
|
||||
<p>2. Our strategy builds on our unique strengths including our ownership model.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg7_2" width="800" height="200"></svg>
|
||||
<p>3. Our strategy outlines key activities and milestones that ensure our employee owners know how to contribute to a successful business.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg7_3" width="800" height="200"></svg>
|
||||
<p>4. Our strategic approach to key markets ensures success by drawing on the experience and insights of our employee owners.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg7_4" width="800" height="200"></svg>
|
||||
|
||||
<p>Q29 Please add any additional comments:</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q30 Great EO Innovation</h3>
|
||||
|
||||
<p>1. Our employee owners understand how our business works and delivers results.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg8_1" width="800" height="200"></svg>
|
||||
<p>2. Our employee owners are committed to driving continuous improvement and growth.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg8_2" width="800" height="200"></svg>
|
||||
<p>3. Our employee owners consistently contribute ideas that improve our business.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg8_3" width="800" height="200"></svg>
|
||||
<p>4. We provide clear channels and processes to capture, consider and provide feedback on new ideas and suggestions.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg8_4" width="800" height="200"></svg>
|
||||
<p>5. We successfully prioritise and manage these ideas to deliver improvements to our business.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg8_5" width="800" height="200"></svg>
|
||||
|
||||
<p>Q31 Please add any additional comments:</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Great EO Advantage</h3>
|
||||
|
||||
<p>1. We leverage our EO status to differentiate our customer brand to win against our competitors in the market.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg9_1" width="800" height="200"></svg>
|
||||
<p>2. We leverage our EO status to differentiate our employer brand to strengthen our people proposition and attract talent.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg9_2" width="800" height="200"></svg>
|
||||
<p>3. We leverage our EO status to differentiate our company brand to develop and retain high-quality relationships with suppliers and delivery partners.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg9_3" width="800" height="200"></svg>
|
||||
|
||||
<p>Q33 Please add any additional comments:</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
|
||||
|
||||
<h2>4. EO Results</h2>
|
||||
|
||||
<p>Well-run EO businesses ensure decision-making is informed by relevant data and insights from inside the business and by benchmarking themselves against others. This helps set strategic focus, direct operational effort, ensure continuous improvement and drive better business performance.</p>
|
||||
|
||||
<p><strong>Do you have the right EO data and insights to make this happen?</strong></p>
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_results" width="500" height="500"></svg>
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_results_RAG" width="500" height="500"></svg>
|
||||
<svg class = "no-axis no-text big-number" style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_results_test" width="500" height="500"></svg>
|
||||
<hr>
|
||||
|
||||
<h3>Q35 Great EO Measurement</h3>
|
||||
|
||||
<p>1. We are collecting data within the business that helps us understand how we benefit from our EO model.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg10_1" width="800" height="200"></svg>
|
||||
<p>2. We measure and compare our EO performance with other similar EO businesses.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg10_2" width="800" height="200"></svg>
|
||||
<p>3. We measure and compare our performance with other businesses in our sector (regardless of their ownership model).</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg10_3" width="800" height="200"></svg>
|
||||
<p>4. We pay attention to our impacts on more than just our employee owners (e.g. our customers, community or planet).</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg10_4" width="800" height="200"></svg>
|
||||
|
||||
<p>Q36 Please add any additional comments:</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q37 Great EO Evaluation</h3>
|
||||
|
||||
<p>1. Our leaders and governance bodies use relevant EO data and insights to increase engagement levels among our people.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg11_1" width="800" height="200"></svg>
|
||||
<p>2. Our leaders and governance bodies use relevant EO data and insights to strengthen our ownership culture.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg11_2" width="800" height="200"></svg>
|
||||
<p>3. Our leaders and governance bodies use relevant EO data and insights to grow our business.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg11_3" width="800" height="200"></svg>
|
||||
<p>4. Our leaders and governance bodies use relevant EO data and insights to meet the aspirations of our current employee owners.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg11_4" width="800" height="200"></svg>
|
||||
|
||||
<p>Q38 Please add any additional comments:</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Q39 Great EO Impact</h3>
|
||||
|
||||
<p>1. EO data and insights helps us demonstrate that our EO model encourages collaboration and team-working.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_1" width="800" height="200"></svg>
|
||||
<p>2. EO data and insights help us demonstrate that our EO model promotes engagement and job satisfaction.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_2" width="800" height="200"></svg>
|
||||
<p>3. EO data and insights help us demonstrate that our EO model improves health and well-being for employee owners.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_3" width="800" height="200"></svg>
|
||||
<p>4. EO data and insights help us demonstrate that our EO model supports colleagues’ skills and capability.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_4" width="800" height="200"></svg>
|
||||
<p>5. EO data and insights help us demonstrate that our EO model encourages strong ideas from colleagues.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_5" width="800" height="200"></svg>
|
||||
<p>6. EO data and insights help us demonstrate that our EO model helps us recruit and retain talent.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_6" width="800" height="200"></svg>
|
||||
<p>7. EO data and insights help us demonstrate that our EO model boosts personal commitment and productivity.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_7" width="800" height="200"></svg>
|
||||
<p>8. EO data and insights help us demonstrate that our EO model increases profitability.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_8" width="800" height="200"></svg>
|
||||
<p>9. EO data and insights help us demonstrate that our EO model encourages more contribution to the local community.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_9" width="800" height="200"></svg>
|
||||
<p>10. EO data and insights help us demonstrate that our EO model encourages a focus on protecting the planet.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg12_10" width="800" height="200"></svg>
|
||||
|
||||
<p>Q40 Please add any additional comments:</p>
|
||||
|
||||
<hr>
|
||||
|
||||
</body>
|
||||
</html>
|
177
eoq_code/savesvg.js
Normal file
177
eoq_code/savesvg.js
Normal file
@ -0,0 +1,177 @@
|
||||
function doBigWhiteTriangle(id){
|
||||
const svg = document.getElementById(id);
|
||||
|
||||
// Triangle settings
|
||||
const sideLength = 740;
|
||||
const centerX = 435;
|
||||
const centerY = 493;
|
||||
const angleOffset = -90 * (Math.PI / 180); // Start at top
|
||||
|
||||
// Generate 3 equilateral triangle points
|
||||
const points = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const angle = angleOffset + i * (2 * Math.PI / 3); // 120° steps
|
||||
const x = centerX + (sideLength / Math.sqrt(3)) * Math.cos(angle);
|
||||
const y = centerY + (sideLength / Math.sqrt(3)) * Math.sin(angle);
|
||||
points.push(`${x},${y}`);
|
||||
}
|
||||
|
||||
// Create the triangle
|
||||
const triangle = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
|
||||
triangle.setAttribute("points", points.join(" "));
|
||||
triangle.setAttribute("fill", "white");
|
||||
//triangle.setAttribute("stroke", "black"); // Optional
|
||||
//triangle.setAttribute("stroke-width", "2"); // Optional
|
||||
svg.appendChild(triangle);
|
||||
}
|
||||
|
||||
function doLittleWhiteTriangle(id){
|
||||
const svg = document.getElementById(id);
|
||||
|
||||
// Triangle settings
|
||||
const sideLength = 369;
|
||||
const centerX = 250;
|
||||
const centerY = 350;
|
||||
const angleOffset = -90 * (Math.PI / 180); // Start at top
|
||||
|
||||
// Generate 3 equilateral triangle points
|
||||
const points = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const angle = angleOffset + i * (2 * Math.PI / 3); // 120° steps
|
||||
const x = centerX + (sideLength / Math.sqrt(3)) * Math.cos(angle);
|
||||
const y = centerY + (sideLength / Math.sqrt(3)) * Math.sin(angle);
|
||||
points.push(`${x},${y}`);
|
||||
}
|
||||
|
||||
// Create the triangle
|
||||
const triangle = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
|
||||
triangle.setAttribute("points", points.join(" "));
|
||||
triangle.setAttribute("fill", "white");
|
||||
//triangle.setAttribute("stroke", "black"); // Optional
|
||||
//triangle.setAttribute("stroke-width", "2"); // Optional
|
||||
svg.appendChild(triangle);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
function inlineStyles(svgElement) {
|
||||
const allElements = svgElement.querySelectorAll('*');
|
||||
const computedStyles = window.getComputedStyle(svgElement);
|
||||
|
||||
// Apply style to root SVG element
|
||||
svgElement.setAttribute("style", computedStyles.cssText || "");
|
||||
|
||||
// Recursively apply computed styles to each element
|
||||
allElements.forEach(el => {
|
||||
const computed = window.getComputedStyle(el);
|
||||
let inline = '';
|
||||
for (let i = 0; i < computed.length; i++) {
|
||||
const key = computed[i];
|
||||
const value = computed.getPropertyValue(key);
|
||||
inline += `${key}:${value};`;
|
||||
}
|
||||
el.setAttribute('style', inline);
|
||||
});
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
function inlineStyles(svgElement) {
|
||||
const allElements = svgElement.querySelectorAll('*');
|
||||
|
||||
allElements.forEach(el => {
|
||||
const computed = window.getComputedStyle(el);
|
||||
const style = [];
|
||||
|
||||
for (let i = 0; i < computed.length; i++) {
|
||||
const key = computed[i];
|
||||
let value = computed.getPropertyValue(key);
|
||||
|
||||
// Replace currentColor with resolved color
|
||||
if (value === 'currentcolor') {
|
||||
value = computed.color;
|
||||
}
|
||||
|
||||
style.push(`${key}:${value};`);
|
||||
}
|
||||
|
||||
el.setAttribute('style', style.join(' '));
|
||||
});
|
||||
|
||||
// Also inline root <svg>
|
||||
const svgComputed = window.getComputedStyle(svgElement);
|
||||
let rootStyle = '';
|
||||
for (let i = 0; i < svgComputed.length; i++) {
|
||||
const key = svgComputed[i];
|
||||
let value = svgComputed.getPropertyValue(key);
|
||||
if (value === 'currentcolor') {
|
||||
value = svgComputed.color;
|
||||
}
|
||||
rootStyle += `${key}:${value};`;
|
||||
}
|
||||
svgElement.setAttribute('style', rootStyle);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
function makeSvgRightClickable(id) {
|
||||
const svg = document.getElementById(id);
|
||||
inlineStyles(svg);
|
||||
|
||||
// Ensure SVG has proper namespace
|
||||
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
||||
|
||||
// Serialize SVG
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
|
||||
// Create image element
|
||||
const img = document.createElement("img");
|
||||
img.src = url;
|
||||
img.width = svg.width.baseVal.value;
|
||||
img.height = svg.height.baseVal.value;
|
||||
img.alt = "Right-click to save SVG image";
|
||||
|
||||
// Replace or insert after SVG
|
||||
//svg.style.display = "none"; // hide original
|
||||
svg.parentNode.insertBefore(img, svg.nextSibling);
|
||||
}
|
||||
|
||||
// Call the function after D3 has drawn the SVG
|
||||
//makeSvgRightClickable("id");
|
||||
*/
|
||||
|
||||
|
||||
function makeSvgRightClickable(id) {
|
||||
const svg = document.getElementById(id);
|
||||
inlineStyles(svg);
|
||||
|
||||
// Set namespace
|
||||
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
||||
|
||||
// Ensure width and height are set
|
||||
if (!svg.hasAttribute("width")) svg.setAttribute("width", svg.getBoundingClientRect().width);
|
||||
if (!svg.hasAttribute("height")) svg.setAttribute("height", svg.getBoundingClientRect().height);
|
||||
|
||||
// Serialize
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
|
||||
// Create image element
|
||||
const img = document.createElement("img");
|
||||
img.src = url;
|
||||
img.width = svg.getAttribute("width");
|
||||
img.height = svg.getAttribute("height");
|
||||
img.alt = "Right-click to save SVG image";
|
||||
|
||||
svg.style.display = "none";
|
||||
svg.parentNode.insertBefore(img, svg.nextSibling);
|
||||
}
|
||||
|
||||
|
209
getData.php
209
getData.php
@ -1,109 +1,152 @@
|
||||
|
||||
<?php
|
||||
// Set up constants:
|
||||
$apiToken="nCqnPnrt9HplICvOWhratTIrwkxqe7pILQ524GJG";
|
||||
$dataCenter = "fra1"; // Example: "us-east-1"
|
||||
$surveyId = "SV_3pyZVUNpxXm1PZI"; //Survey ID
|
||||
$urveyId ="SV_bmiHoSHYWIgGM3I"; //Survey ID
|
||||
//$surveyId = "SV_bmiHoSHYWIgGM3I"; //Survey ID
|
||||
// Settings
|
||||
$config = require 'config.php';
|
||||
require_once 'db.php'; // include the connection
|
||||
$apiToken = $config['api_token'];
|
||||
$dataCentre = $config['data_centre'];
|
||||
$surveyId = $_POST['survey_id'] ?? null;
|
||||
|
||||
// API endpoint URLs
|
||||
$exportUrl = "https://$dataCenter.qualtrics.com/API/v3/surveys/$surveyId/export-responses";
|
||||
$statusUrl = "https://$dataCenter.qualtrics.com/API/v3/surveys/$surveyId/export-responses/";
|
||||
$fileUrl = "https://$dataCenter.qualtrics.com/API/v3/surveys/$surveyId/export-responses/";
|
||||
if (!$surveyId) {
|
||||
$surveyId="SV_bD838sNKZEmi6Tc"; // *****FIXME***** JUST FOR DEVELOPMENT
|
||||
//die("No survey ID provided.");
|
||||
}
|
||||
|
||||
// Step 1: Start Export
|
||||
$baseUrl = "https://$dataCentre.qualtrics.com/API/v3";
|
||||
$headers = [
|
||||
"X-API-TOKEN: $apiToken",
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
$ch = curl_init($exportUrl);
|
||||
// Step 1: Start the export (with compress = false)
|
||||
$exportUrl = "$baseUrl/surveys/$surveyId/export-responses/";
|
||||
$postFields = json_encode([
|
||||
"format" => "json",
|
||||
"compress" => false
|
||||
]);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $exportUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["format" => "json"]));
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
echo "cURL Error: " . curl_error($ch);
|
||||
curl_close($ch);
|
||||
exit;
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if (!$response) {
|
||||
die("Error starting export: " . curl_error($ch));
|
||||
$result = json_decode($response, true);
|
||||
echo "<pre>".$surveyId." ".$exportUrl."</pre>";
|
||||
if (!isset($result['result']['progressId'])) {
|
||||
|
||||
echo "<pre>Export API Response:\n";
|
||||
print_r($response);
|
||||
echo "</pre>";
|
||||
die("Failed to start export.\n$response");
|
||||
}
|
||||
$progressId = $result['result']['progressId'];
|
||||
|
||||
echo "Polling for completion";
|
||||
|
||||
|
||||
|
||||
// Step 2: Poll for completion
|
||||
$progressUrl = "$exportUrl/$progressId";
|
||||
do {
|
||||
sleep(2); // avoid hammering the API
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $progressUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
$status = $result['result']['status'] ?? 'failed';
|
||||
} while ($status !== 'complete' && $status !== 'failed');
|
||||
|
||||
if ($status !== 'complete') {
|
||||
die("Export failed or timed out.\n$response");
|
||||
}
|
||||
|
||||
$exportData = json_decode($response, true);
|
||||
$progressId = $exportData['result']['progressId'];
|
||||
$fileId = $result['result']['fileId'];
|
||||
|
||||
// Step 2: Check Export Status
|
||||
$status = 'inProgress';
|
||||
while ($status != 'complete') {
|
||||
// Sleep for 5 seconds before checking again
|
||||
sleep(5);
|
||||
// Step 4: Download the file (uncompressed JSON)
|
||||
$downloadUrl = "$exportUrl/$fileId/file";
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $downloadUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
$statusCh = curl_init($statusUrl . $progressId);
|
||||
curl_setopt($statusCh, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($statusCh, CURLOPT_HTTPHEADER, $headers);
|
||||
$statusResponse = curl_exec($statusCh);
|
||||
curl_close($statusCh);
|
||||
if (curl_errno($ch)) {
|
||||
die("Error downloading the file: " . curl_error($ch));
|
||||
}
|
||||
|
||||
if (!$statusResponse) {
|
||||
die("Error checking status: " . curl_error($statusCh));
|
||||
curl_close($ch);
|
||||
|
||||
// Decode the response
|
||||
$data = json_decode($response, true);
|
||||
|
||||
//Save surveyId
|
||||
//insertSurveyIfNotExists($identifier, $description); // The identifier is actually the surveyId, and the description isn't included at this point.
|
||||
$surveyIndex = getOrCreateSurvey($surveyId, "");
|
||||
|
||||
echo "-------".$surveyIndex."-----------";
|
||||
|
||||
// $data = $result['result'][''] ?? [];
|
||||
echo "Total responses: " . count($data['responses']) . "\n\n<br>";
|
||||
|
||||
if (isset($data['responses']) && is_array($data['responses'])) {
|
||||
foreach ($data['responses'] as $response) {
|
||||
echo "Response ID: " . $response['responseId'] . "\n";
|
||||
//Insert responseId into the database
|
||||
//
|
||||
$startDate = new DateTime($response['values']['startDate']);
|
||||
$startDateFormatted = $startDate->format('Y-m-d H:i:s');
|
||||
$endDate = new DateTime($response['values']['endDate']);
|
||||
$endDateFormatted = $endDate->format('Y-m-d H:i:s');
|
||||
$recordedDate = new DateTime($response['values']['recordedDate']);
|
||||
$recordedDateFormatted = $recordedDate->format('Y-m-d H:i:s');
|
||||
|
||||
$responseData = [
|
||||
'responseId' => $response['responseId'],
|
||||
'surveyId' => $surveyIndex,
|
||||
'startDate' => $startDateFormatted,
|
||||
'endDate' => $endDateFormatted,
|
||||
'status' => $response['values']['status'],
|
||||
'ipAddress' => $response['values']['ipAddress'],
|
||||
'progress' => $response['values']['progress'],
|
||||
'duration' => $response['values']['duration'],
|
||||
'finished' => $response['values']['finished'],
|
||||
'recordedDate' => $recordedDateFormatted,
|
||||
'locationLatitude' => $response['values']['locationLatitude'],
|
||||
'locationLongitude' => $response['values']['locationLongitude'],
|
||||
'distributionChannel' => $response['values']['distributionChannel'],
|
||||
'userLanguage' => $response['values']['userLanguage']
|
||||
];
|
||||
|
||||
|
||||
$responseIndex = upsertResponse($responseData);
|
||||
// Now read in the answers. These all start with QID????? etc
|
||||
$answers = $response['values'];
|
||||
|
||||
insertAnswers($pdo, $surveyIndex, $responseIndex, $answers);
|
||||
|
||||
// Prepare and execute the INSERT statement
|
||||
//
|
||||
//
|
||||
}
|
||||
|
||||
//print_r(json_decode($statusResponse, true));
|
||||
//exit(0);
|
||||
|
||||
$statusData = json_decode($statusResponse, true);
|
||||
$status = $statusData['result']['status'];
|
||||
|
||||
if ($status == 'failed') {
|
||||
die("Export failed!");
|
||||
}
|
||||
|
||||
echo "Waiting for export to complete... Status: $status\n";
|
||||
}
|
||||
|
||||
// Step 3: Download the Exported File
|
||||
$fileId = $statusData['result']['fileId'];
|
||||
|
||||
$fileCh = curl_init($fileUrl . $fileId . '/file');
|
||||
curl_setopt($fileCh, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($fileCh, CURLOPT_HTTPHEADER, $headers);
|
||||
$fileContent = curl_exec($fileCh);
|
||||
curl_close($fileCh);
|
||||
|
||||
if (!$fileContent) {
|
||||
die("Error downloading the file: " . curl_error($fileCh));
|
||||
}
|
||||
|
||||
// Save the file locally
|
||||
file_put_contents("survey_responses.zip", $fileContent);
|
||||
|
||||
echo "Survey data has been downloaded successfully!\n";
|
||||
|
||||
// Path to the ZIP file
|
||||
$zipFile = './survey_responses.zip'; // Specify the path to your ZIP file
|
||||
$extractTo = 'survey_responses.json'; // Specify the folder to extract the contents to
|
||||
|
||||
// Create a new ZipArchive object
|
||||
$zip = new ZipArchive();
|
||||
|
||||
// Open the ZIP file
|
||||
if ($zip->open($zipFile) === TRUE) {
|
||||
// Extract all the contents to the specified folder
|
||||
$zip->extractTo($extractTo);
|
||||
$zip->close(); // Close the ZIP file
|
||||
|
||||
echo "ZIP file extracted successfully!";
|
||||
} else {
|
||||
echo "Failed to open the ZIP file.";
|
||||
echo "No responses found.";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
?>
|
||||
|
||||
|
||||
|
||||
|
@ -1,11 +1,15 @@
|
||||
|
||||
<?php
|
||||
// Set up constants:
|
||||
$apiToken="nCqnPnrt9HplICvOWhratTIrwkxqe7pILQ524GJG";
|
||||
$apiToken="GZjoFiLmb2j62s8AHmMWKN25BZjGBhsU5ez4cwjn";
|
||||
//$apiToken="nCqnPnrt9HplICvOWhratTIrwkxqe7pILQ524GJG";
|
||||
$dataCenter = "fra1"; // Example: "us-east-1"
|
||||
$surveyId = "SV_3pyZVUNpxXm1PZI"; //Survey ID
|
||||
$urveyId ="SV_bmiHoSHYWIgGM3I"; //Survey ID
|
||||
$surveyId = "SV_cAstEvm4ZrPaqGi"; //Framework
|
||||
$surveyId = "SV_3pyZVUNpxXm1PZI"; //Template 2nd April
|
||||
$surveyId ="SV_eKy96PPsL4JVZSC"; //Tullis Russell
|
||||
//$surveyId = "SV_bmiHoSHYWIgGM3I"; //Survey ID
|
||||
$surveyId = "SV_bD838sNKZEmi6Tc"; //Template 15 May 2025
|
||||
$surveyId = "SV_cwKjMqAqGxImjMG"; //
|
||||
|
||||
// API endpoint URLs
|
||||
$exportUrl = "https://$dataCenter.qualtrics.com/API/v3/surveys/$surveyId/export-responses";
|
||||
@ -31,6 +35,7 @@ if (!$response) {
|
||||
}
|
||||
|
||||
$exportData = json_decode($response, true);
|
||||
echo $response;
|
||||
$progressId = $exportData['result']['progressId'];
|
||||
|
||||
// Step 2: Check Export Status
|
||||
|
65
getQuestions.php
Normal file
65
getQuestions.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
// Load config
|
||||
$config = require 'config.php';
|
||||
$apiToken = $config['api_token'];
|
||||
$dataCenter = $config['data_centre'];
|
||||
|
||||
// Get the survey ID from POST
|
||||
$surveyId = $_POST['survey_id'] ?? null;
|
||||
if (!$surveyId) {
|
||||
$surveyId="SV_cAstEvm4ZrPaqGi";
|
||||
#die("No survey ID provided.");
|
||||
}
|
||||
|
||||
// Build URL
|
||||
$baseUrl = "https://$dataCenter.qualtrics.com/API/v3";
|
||||
$questionsUrl = "$baseUrl/survey-definitions/$surveyId/questions";
|
||||
|
||||
// Set headers
|
||||
$headers = [
|
||||
"X-API-TOKEN: $apiToken",
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
// Make request
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $questionsUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
die("Request error: " . curl_error($ch));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// Output the raw JSON
|
||||
//header('Content-Type: application/json');
|
||||
//echo $response;
|
||||
//var_dump($response);
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$questions = $data['result']['elements'] ?? [];
|
||||
echo '<pre>';
|
||||
print_r(array_slice($questions, 0, 1)); // Show one question object
|
||||
echo '</pre>';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Survey Questions</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Survey Questions</h1>
|
||||
|
||||
<ul>
|
||||
<?php foreach ($questions as $qid => $question): ?>
|
||||
<li>
|
||||
<strong><?= htmlspecialchars($qid) ?>:</strong>
|
||||
<?= htmlspecialchars($question['QuestionText'] ?? '[No question text]') ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
68
getSurveys.php
Normal file
68
getSurveys.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
// Set your API key and data center
|
||||
$apiToken = 'GZjoFiLmb2j62s8AHmMWKN25BZjGBhsU5ez4cwjn';
|
||||
$dataCenter = 'fra1'; // Your data center, e.g., 'fra1'
|
||||
|
||||
// Set the endpoint URL
|
||||
$baseUrl = "https://$dataCenter.qualtrics.com/API/v3/surveys";
|
||||
|
||||
// Initialize cURL session
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $baseUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"X-API-TOKEN: $apiToken",
|
||||
"Content-Type: application/json"
|
||||
]);
|
||||
|
||||
// Execute the request
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// Check for cURL errors
|
||||
if(curl_errno($ch)){
|
||||
die('Request Error: ' . curl_error($ch));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// Decode the response
|
||||
$result = json_decode($response, true);
|
||||
$surveys = $result['result']['elements'] ?? [];
|
||||
|
||||
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Select a Qualtrics Survey</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Select a Survey</h1>
|
||||
|
||||
<?php if (empty($surveys)): ?>
|
||||
<p>No surveys found or an error occurred.</p><br><br><hr><br>
|
||||
|
||||
<pre><?php echo print_r($result); ?></pre>
|
||||
<pre><?php
|
||||
|
||||
|
||||
?></pre>
|
||||
<?php else: ?>
|
||||
<form method="POST" action="getData.php">
|
||||
<label for="survey_id">Choose a survey to download data and produce reports:</label>
|
||||
<select name="survey_id" id="survey_id">
|
||||
<?php foreach ($surveys as $survey): ?>
|
||||
<option value="<?= htmlspecialchars($survey['id']) ?>">
|
||||
<?= htmlspecialchars($survey['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<br><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
67
get_qid_counts.html
Normal file
67
get_qid_counts.html
Normal file
@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>QID Value Counts</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 2em; }
|
||||
label, input, button { font-size: 1em; margin: 0.5em 0; }
|
||||
ul { list-style-type: none; padding-left: 0; }
|
||||
li { padding: 0.3em 0; }
|
||||
.error { color: red; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Query Value Counts by QID</h1>
|
||||
|
||||
<form id="qidForm">
|
||||
<label for="qidInput">Enter QID:</label><br />
|
||||
<input type="text" id="qidInput" name="qid" required />
|
||||
<button type="submit">Get Counts</button>
|
||||
</form>
|
||||
|
||||
<div id="results"></div>
|
||||
|
||||
<script>
|
||||
document.getElementById('qidForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const qid = document.getElementById('qidInput').value.trim();
|
||||
const resultsDiv = document.getElementById('results');
|
||||
resultsDiv.innerHTML = 'Loading...';
|
||||
|
||||
fetch('get_qid_counts.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({qid})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
resultsDiv.innerHTML = `<p class="error">${data.error}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
resultsDiv.innerHTML = '<p>No data found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<h2>Value Counts for ' + qid + '</h2><ul>';
|
||||
data.forEach(item => {
|
||||
html += `<li><strong>Value ${item.value}:</strong> ${item.count}</li>`;
|
||||
});
|
||||
html += '</ul>';
|
||||
|
||||
resultsDiv.innerHTML = html;
|
||||
})
|
||||
.catch(err => {
|
||||
resultsDiv.innerHTML = `<p class="error">Error: ${err.message}</p>`;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
67
get_qid_counts.php
Normal file
67
get_qid_counts.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Basic input validation and sanitization
|
||||
if (!isset($_POST['qid']) || empty($_POST['qid'])) {
|
||||
echo json_encode(['error' => 'Missing QID parameter']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$qid = $_POST['qid'];
|
||||
|
||||
// Database connection (adjust credentials accordingly)
|
||||
//
|
||||
$config = require 'config.php';
|
||||
$host = $config['db_host'];
|
||||
$db = $config['db_name'];
|
||||
$user = $config['db_user'];
|
||||
$pass = $config['db_pass'];
|
||||
$charset = 'utf8mb4';
|
||||
|
||||
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
$sql = "
|
||||
SELECT value, COUNT(*) AS count
|
||||
FROM Answers
|
||||
WHERE QID = :qid
|
||||
AND value BETWEEN -3 AND 3
|
||||
GROUP BY value
|
||||
ORDER BY value
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute(['qid' => $qid]);
|
||||
$results = $stmt->fetchAll();
|
||||
|
||||
// Initialize array with all possible values from -3 to 3 with zero counts
|
||||
$counts = [];
|
||||
for ($i = -3; $i <= 3; $i++) {
|
||||
$counts[$i] = 0;
|
||||
}
|
||||
|
||||
// Fill in counts from query results
|
||||
foreach ($results as $row) {
|
||||
$counts[(int)$row['value']] = (int)$row['count'];
|
||||
}
|
||||
|
||||
// Return as JSON array of objects [{value: -3, count: 5}, ...]
|
||||
$response = [];
|
||||
foreach ($counts as $value => $count) {
|
||||
$response[] = ['value' => $value, 'count' => $count];
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
148
populateGraphics.js
Normal file
148
populateGraphics.js
Normal file
@ -0,0 +1,148 @@
|
||||
function RAGGED(average){
|
||||
// This returns the correct colour red, amber or gree depending on the value presented
|
||||
if (average <= -1) {
|
||||
return "red";
|
||||
}
|
||||
if (average >= 1) {
|
||||
return "green";
|
||||
}
|
||||
return "#ffbf00";
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getAverage(prefix) {
|
||||
console.log("Prefix:", prefix);
|
||||
|
||||
const elements = Array.from(document.querySelectorAll(`svg[id^="${prefix}"] tspan.average`));
|
||||
|
||||
const numbers = elements.map(el => {
|
||||
const text = el.textContent;
|
||||
//console.log("Raw text content:", text);
|
||||
const value = parseFloat(text);
|
||||
//console.log("Parsed value:", value);
|
||||
return value;
|
||||
}).filter(n => !isNaN(n));
|
||||
|
||||
//console.log("Parsed numeric values:", numbers);
|
||||
|
||||
if (numbers.length === 0) {
|
||||
console.warn("No valid numbers found!");
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const avg = numbers.reduce((sum, n) => sum + n, 0) / numbers.length;
|
||||
//console.log("Average of averages:", avg);
|
||||
return avg;
|
||||
}
|
||||
|
||||
|
||||
function loaded(){
|
||||
console.log("loaded");
|
||||
|
||||
let amber = '#ffbf00';
|
||||
//doBigWhiteTriangle('svg1');
|
||||
//drawtriangle('#svg1','Roles','#008845',[0.5,0.6,0.5],'red', { x: 0, y: 350 },-0.7);
|
||||
//drawtriangle('#svg1','Actions','#b2c8c4',[0.1,0.6,0.5],'green',{ x: 370, y: 350 },2.3);
|
||||
//drawtriangle('#svg1','Approach','#ed4c0c',[0.3,0.6,0.5],amber,{ x: 185, y: 30 },1.4);
|
||||
//drawtriangleinverted('#svg1','Impact','#74469c',[0.7,0.6,0.8],'green',{ x: 185, y: 243},2.4);
|
||||
//makeSvgRightClickable('svg1');
|
||||
|
||||
|
||||
|
||||
//doLittleWhiteTriangle('svg2');
|
||||
//drawtriangle('#svg2','Roles','#008845',[0.5,0.6,0.5],'', { x: 0, y: 100 },-0.7);
|
||||
|
||||
//EO ROLES
|
||||
//Great Employee Owners
|
||||
let average = doBarData('#svg1_1','QID35_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg1_2','QID35_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg1_3','QID35_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg1_4','QID35_9');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
doBarData('#svg1_5','QID35_10');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
|
||||
|
||||
|
||||
//Great EO Leaders
|
||||
average = doBarData('#svg2_1','QID35_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg2_2','QID35_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg2_3','QID35_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg2_4','QID35_7');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
//doBarData('#svg2_5','QID35_10');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
|
||||
|
||||
//Great EO Governance
|
||||
average = doBarData('#svg3_1','QID35_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
doBarData('#svg3_2','QID35_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
doBarData('#svg3_3','QID35_8');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
doBarData('#svg3_4','QID35_7');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
doBarData('#svg3_5','QID35_10');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
doBarData('#svg3_6','QID35_10');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
|
||||
|
||||
|
||||
// Make sure that evreything is loaded!
|
||||
setTimeout(() => {
|
||||
console.log("Waited 2 seconds");
|
||||
// We now have access to all of the averages, so can calculate the basic triangle.
|
||||
|
||||
let svg1_avg = (getAverage("svg1_")+3)/7;
|
||||
let svg2_avg = (getAverage("svg2_")+3)/7;
|
||||
let svg3_avg = (getAverage("svg3_")+3)/7;
|
||||
let roles_avg = (7*(svg1_avg+svg2_avg+svg3_avg)/3)-3; // this converts the scales lengths to the correct value
|
||||
|
||||
console.log(svg1_avg,svg2_avg,svg3_avg, roles_avg);
|
||||
|
||||
|
||||
doLittleWhiteTriangle('svg_roles');
|
||||
//drawtriangle('#svg_roles','Roles','#008845',[0.25,0.5,0.75],'', { x: 0, y: 100 },roles_avg.toFixed(1));
|
||||
drawtriangle('#svg_roles','Roles','#008845',[svg1_avg,svg2_avg,svg3_avg],'', { x: 0, y: 100 },roles_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_roles');
|
||||
|
||||
doLittleWhiteTriangle('svg_roles_RAG');
|
||||
drawtriangle('#svg_roles_RAG','Roles','#008845',[svg1_avg,svg2_avg,svg3_avg],RAGGED(roles_avg), { x: 0, y: 100 },roles_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_roles_RAG');
|
||||
|
||||
doLittleWhiteTriangle('svg_roles_test');
|
||||
drawtriangle('#svg_roles_test','Roles','#008845',[0.95,0.95,0.95],RAGGED(roles_avg), { x: 0, y: 100 },roles_avg.toFixed(1));
|
||||
//makeSvgRightClickable('svg_roles_test');
|
||||
|
||||
|
||||
}, 500);
|
||||
//EO APPROACH
|
||||
|
||||
|
||||
|
||||
//drawBar('#svg1_1','QID34_7');
|
||||
//makeSvgRightClickable('svg1_1');
|
||||
|
||||
//drawBar('#svg1_2');
|
||||
//makeSvgRightClickable('svg1_2');
|
||||
|
||||
//drawBar('#svg1_3');
|
||||
//makeSvgRightClickable('svg1_3');
|
||||
|
||||
//drawBar('#svg1_4');
|
||||
//makeSvgRightClickable('svg1_4');
|
||||
|
||||
//drawBar('#svg1_5');
|
||||
//makeSvgRightClickable('svg1_5');
|
||||
|
||||
|
||||
}
|
351
reportTemplate.html
Normal file
351
reportTemplate.html
Normal file
@ -0,0 +1,351 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>EOQ Output</title>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<script src="./drawtriangle.js"></script>
|
||||
<script src="./drawtriangleinverted.js"></script>
|
||||
<script src="./drawbar.js"></script>
|
||||
<script src="./savesvg.js"></script>
|
||||
<script src="./populateGraphics.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
svg {
|
||||
background: none;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.axis line {
|
||||
stroke: black;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
line.tick {
|
||||
stroke: black;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
polygon.grid {
|
||||
fill: none;
|
||||
stroke: rgba(200, 200, 200, 0.5); /* light grey with 50% opacity */
|
||||
stroke-dasharray: 2,2;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-anchor: middle;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="loaded()">
|
||||
|
||||
|
||||
<h1>TITLE</h1>
|
||||
|
||||
<p>Dates: TO </p>
|
||||
|
||||
<p>Number of completed surveys: </p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h2>A Health Check for your Employee Owned Business</h2>
|
||||
|
||||
<p>Employee ownership (EO) has the potential to deliver outstanding benefits to individual owners and drive exceptional company performance.</p>
|
||||
|
||||
<p>However, research shows, for that to happen, a business must have excellent EO management practices in place.</p>
|
||||
|
||||
<p>To help identify EO best practice, we developed the EO Framework. It is built on insights gathered directly from leaders, senior decision-makers and employee owners on how they have successfully implemented impactful EO.</p>
|
||||
|
||||
<p>Developed by Great EO's founding partners in collaboration with the Employee Ownership Association, the EO Framework sets out four simple areas of EO management best practice:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>EO Roles:</strong> Clearly defined EO roles through which leaders, trustees and employee owners all understand the part they play in making EO successful.</li>
|
||||
<li><strong>EO Approach:</strong> Strong EO core practices that empower employees to contribute to success.</li>
|
||||
<li><strong>EO Actions:</strong> Specific areas of EO management practice that can harness the full potential of employee ownership to strengthen strategy, innovation, and brand difference.</li>
|
||||
<li><strong>EO Results:</strong> Structured and consistent ways of measuring EO practice to track and enhance owner benefits and business outcomes.</li>
|
||||
</ul>
|
||||
|
||||
<p>To bring the EO Framework to life, we have created a simple health check - the eoQ™ test - to help you assess the strength of your current EO practice and signpost areas for possible improvement. It should take up to 20-30 minutes to complete the questionnaire.</p>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg_pyramid" width="800" height="200"></svg>
|
||||
<hr>
|
||||
|
||||
|
||||
<h2>1. EO Roles</h2>
|
||||
|
||||
<p>It is vital that individual leaders, trustees, employee representatives and all employee owners understand their roles to ensure a successful EO business.</p>
|
||||
|
||||
<p><strong>To what extent does everyone fulfil their responsibilities in your business?</strong></p>
|
||||
|
||||
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_roles" width="500" height="500"></svg>
|
||||
<svg style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_roles_RAG" width="500" height="500"></svg>
|
||||
<svg class = "no-axis no-text big-number" style="display: inline-block" xmlns="http://www.w3.org/2000/svg" id="svg_roles_test" width="500" height="500"></svg>
|
||||
|
||||
<hr>
|
||||
<h3>Q14 Great Employee Owners</h3>
|
||||
|
||||
<p>1. Our employee owners understand how to think and act like an owner.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_1" width="800" height="200"></svg>
|
||||
<p>2. Our employee owners are committed to making the business stronger.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_2" width="800" height="200"></svg>
|
||||
<p>3. Our employee owners understand how their actions impact the current year’s business results.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_3" width="800" height="200"></svg>
|
||||
<p>4. Our employee owners understand how their actions help to execute our longer-term strategy.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_4" width="800" height="200"></svg>
|
||||
<p>5. Our employee owners understand how their actions strengthen our organisational culture.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg1_5" width="800" height="200"></svg>
|
||||
<p>Q15 Please add any additional comments:</p>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q16 Great EO Leaders</h3>
|
||||
|
||||
<p>1. Our executive leaders (those responsible for leading the business) are committed to realising the full business potential of our employee ownership.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg2_1" width="800" height="200"></svg>
|
||||
<p>2. Our executive leaders regularly engage our people to contribute as owners to our vision and strategy.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg2_2" width="800" height="200"></svg>
|
||||
<p>3. Our executive leaders ensure our people feel connected to our purpose, values, owner mindset and behaviours.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg2_3" width="800" height="200"></svg>
|
||||
<p>4. Our executive leaders are role models for our desired owner behaviours and culture.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg2_4" width="800" height="200"></svg>
|
||||
|
||||
<p>Q17 Please add any additional comments:</p>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q18 Great EO Governance</h3>
|
||||
|
||||
<p>1. All our governance bodies (boards, councils and voice groups) are clear on their role in ensuring long-term success for the business and its employee owners.</p>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_1" width="800" height="200"></svg>
|
||||
<p>2. All our governance bodies work collaboratively and effectively to promote an ownership culture.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_2" width="800" height="200"></svg>
|
||||
<p>3. All our governance bodies involve independent members who bring relevant experience, perspectives, and insights.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_3" width="800" height="200"></svg>
|
||||
<p>4. All our governance bodies effectively use the diverse skills, experiences and opinions of their members.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_4" width="800" height="200"></svg>
|
||||
<p>5. All our governance bodies fairly distribute ownership benefits in a manner that supports an ownership culture.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_5" width="800" height="200"></svg>
|
||||
<p>6. All our governance bodies make decisions in the long-term best interests of the organisation and its stakeholders.</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="svg3_6" width="800" height="200"></svg>
|
||||
|
||||
<p>Q19 Please add any additional comments:</p>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h2>2. EO Approach</h2>
|
||||
<p>EO core practices build a strong ownership culture which drives engagement levels and encourages owner behaviours, including how to promote and protect the business over the long-term.</p>
|
||||
<p><strong>To what extent are you effectively engaging your people as owners and building an ownership mindset?</strong></p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Great EO Culture</h3>
|
||||
|
||||
<p>1. Our behaviours as owners are improving the performance of the business.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>2. Our people processes (recruitment, development, appraisal and reward) encourage and reward our desired owner behaviours.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>3. Our culture creates a safe environment for feedback, learning, growth, health, well-being, inclusion and diversity.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>4. Our culture and support for people develops and retains the right talent.</p>
|
||||
<p>Insert bar chart</p>
|
||||
|
||||
<p>Q22 Please add any additional comments:</p>
|
||||
|
||||
<p>INSERT 3 axis spider graph</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q23 Great EO Engagement</h3>
|
||||
|
||||
<p>1. All of our managers (those responsible for managing people and processes) help ensure our people understand how and where decisions are made in the business.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>2. All of our managers help ensure our planning process, goal setting, and feedback channels enable people to influence decision-making as owners.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>3. All of our managers help ensure our people clearly understand the connection between their owner behaviours and strategy, business performance and reward.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>4. All of our managers help ensure our people are encouraged and empowered as owners to take responsibility for their own performance and contribution.</p>
|
||||
<p>Insert bar chart</p>
|
||||
|
||||
<p>Q24 Please add any additional comments:</p>
|
||||
|
||||
<p>INSERT 3 axis spider graph</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q25 Great EO Stewardship</h3>
|
||||
|
||||
<p>1. During tough times, we balance the current and future needs of our employee owners with those of the organisation.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>2. Our employee owners identify and manage risks to protect our organisation and reputation.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>3. We learn from tough times to become stronger in the future.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>4. Our culture encourages leaders, managers and specialists to develop candidates for their own succession.</p>
|
||||
<p>Insert bar chart</p>
|
||||
|
||||
<p>Q26 Please add any additional comments:</p>
|
||||
|
||||
<p>INSERT 3 axis spider graph</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h2>3. EO Actions</h2>
|
||||
<p>An EO business can use its ownership model to provide commercial advantages as it faces into the world.</p>
|
||||
<p><strong>To what extent are you harnessing the contributions of employees as owners to strengthen the quality and effectiveness of your strategy, innovation and commercial sustainability?</strong></p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q28 Great EO Strategy</h3>
|
||||
|
||||
<p>1. Our vision sets out the compelling benefits of our ownership model.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>2. Our strategy builds on our unique strengths including our ownership model.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>3. Our strategy outlines key activities and milestones that ensure our employee owners know how to contribute to a successful business.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>4. Our strategic approach to key markets ensures success by drawing on the experience and insights of our employee owners.</p>
|
||||
<p>Insert bar chart</p>
|
||||
|
||||
<p>Q29 Please add any additional comments:</p>
|
||||
|
||||
<p>INSERT 3 axis spider graph</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q30 Great EO Innovation</h3>
|
||||
|
||||
<p>1. Our employee owners understand how our business works and delivers results.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>2. Our employee owners are committed to driving continuous improvement and growth.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>3. Our employee owners consistently contribute ideas that improve our business.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>4. We provide clear channels and processes to capture, consider and provide feedback on new ideas and suggestions.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>5. We successfully prioritise and manage these ideas to deliver improvements to our business.</p>
|
||||
<p>Insert bar chart</p>
|
||||
|
||||
<p>Q31 Please add any additional comments:</p>
|
||||
|
||||
<p>INSERT 3 axis spider graph</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Great EO Advantage</h3>
|
||||
|
||||
<p>1. We leverage our EO status to differentiate our customer brand to win against our competitors in the market.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>2. We leverage our EO status to differentiate our employer brand to strengthen our people proposition and attract talent.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>3. We leverage our EO status to differentiate our company brand to develop and retain high-quality relationships with suppliers and delivery partners.</p>
|
||||
<p>Insert bar chart</p>
|
||||
|
||||
<p>Q33 Please add any additional comments:</p>
|
||||
|
||||
<p>INSERT 3 axis spider graph</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h2>4. EO Results</h2>
|
||||
|
||||
<p>Well-run EO businesses ensure decision-making is informed by relevant data and insights from inside the business and by benchmarking themselves against others. This helps set strategic focus, direct operational effort, ensure continuous improvement and drive better business performance.</p>
|
||||
|
||||
<p><strong>Do you have the right EO data and insights to make this happen?</strong></p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q35 Great EO Measurement</h3>
|
||||
|
||||
<p>1. We are collecting data within the business that helps us understand how we benefit from our EO model.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>2. We measure and compare our EO performance with other similar EO businesses.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>3. We measure and compare our performance with other businesses in our sector (regardless of their ownership model).</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>4. We pay attention to our impacts on more than just our employee owners (e.g. our customers, community or planet).</p>
|
||||
<p>Insert bar chart</p>
|
||||
|
||||
<p>Q36 Please add any additional comments:</p>
|
||||
|
||||
<p>INSERT 3 axis spider graph</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q37 Great EO Evaluation</h3>
|
||||
|
||||
<p>1. Our leaders and governance bodies use relevant EO data and insights to increase engagement levels among our people.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>2. Our leaders and governance bodies use relevant EO data and insights to strengthen our ownership culture.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>3. Our leaders and governance bodies use relevant EO data and insights to grow our business.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>4. Our leaders and governance bodies use relevant EO data and insights to meet the aspirations of our current employee owners.</p>
|
||||
<p>Insert bar chart</p>
|
||||
|
||||
<p>Q38 Please add any additional comments:</p>
|
||||
|
||||
<p>INSERT 3 axis spider graph</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h3>Q39 Great EO Impact</h3>
|
||||
|
||||
<p>1. EO data and insights helps us demonstrate that our EO model encourages collaboration and team-working.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>2. EO data and insights help us demonstrate that our EO model promotes engagement and job satisfaction.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>3. EO data and insights help us demonstrate that our EO model improves health and well-being for employee owners.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>4. EO data and insights help us demonstrate that our EO model supports colleagues’ skills and capability.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>5. EO data and insights help us demonstrate that our EO model encourages strong ideas from colleagues.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>6. EO data and insights help us demonstrate that our EO model helps us recruit and retain talent.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>7. EO data and insights help us demonstrate that our EO model boosts personal commitment and productivity.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>8. EO data and insights help us demonstrate that our EO model increases profitability.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>9. EO data and insights help us demonstrate that our EO model encourages more contribution to the local community.</p>
|
||||
<p>Insert bar chart</p>
|
||||
<p>10. EO data and insights help us demonstrate that our EO model encourages a focus on protecting the planet.</p>
|
||||
<p>Insert bar chart</p>
|
||||
|
||||
<p>Q40 Please add any additional comments:</p>
|
||||
|
||||
<p>INSERT 3 axis spider graph</p>
|
||||
|
||||
<hr>
|
||||
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user