Skip to content

Commit

Permalink
Additional options for value labels (#354)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrjohns authored Dec 10, 2024
1 parent f4322e4 commit f7821e9
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 17 deletions.
43 changes: 43 additions & 0 deletions capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,27 @@
"displayName": "Show Value Labels",
"type" : { "bool" : true }
},
"label_position": {
"displayName": "Label Position",
"type": {
"enumeration" : [
{ "displayName" : "Top", "value" : "top" },
{ "displayName" : "Bottom", "value" : "bottom" }
]
}
},
"label_y_offset": {
"displayName": "Label Offset from Top/Bottom (px)",
"type": { "numeric": true }
},
"label_line_offset": {
"displayName": "Label Offset from Connecting Line (px)",
"type": { "numeric": true }
},
"label_angle_offset": {
"displayName": "Label Angle Offset (degrees)",
"type": { "numeric": true }
},
"label_font": {
"displayName": "Label Font",
"type": { "formatting": { "fontFamily": true } }
Expand All @@ -1072,6 +1093,28 @@
"label_colour":{
"displayName": "Label Font Colour",
"type": { "fill": { "solid": { "color": true } } }
},
"label_line_colour":{
"displayName": "Connecting Line Colour",
"type": { "fill": { "solid": { "color": true } } }
},
"label_line_width": {
"displayName": "Connecting Line Width",
"type": { "numeric": true }
},
"label_line_type": {
"displayName": "Connecting Line Type",
"type": {
"enumeration" : [
{ "displayName" : "Solid", "value" : "10 0" },
{ "displayName" : "Dashed", "value" : "10 10" },
{ "displayName" : "Dotted", "value" : "2 5" }
]
}
},
"label_line_max_length": {
"displayName": "Max Connecting Line Length (px)",
"type": { "numeric": true }
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pbiviz.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"displayName":"SPC Charts",
"guid":"PBISPC",
"visualClassName":"Visual",
"version":"1.4.4.15",
"version":"1.4.4.16",
"description":"A PowerBI custom visual for SPC charts",
"supportUrl":"https://github.com/AUS-DOH-Safety-and-Quality/PowerBI-SPC",
"gitHubUrl":"https://github.com/AUS-DOH-Safety-and-Quality/PowerBI-SPC"
Expand Down
105 changes: 92 additions & 13 deletions src/D3 Plotting Functions/drawLabels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,83 @@ import * as d3 from "./D3 Modules";
import type { svgBaseType, Visual } from "../visual";

export default function drawLabels(selection: svgBaseType, visualObj: Visual) {

if (!visualObj.viewModel.inputSettings.settings.label_options.show_labels) {
selection.select(".text-labels").remove();
return;
}

if (selection.select(".text-labels").empty()) {
selection.append("g").classed("text-labels", true);
}

const plotHeight: number = visualObj.viewModel.svgHeight;
const xAxisHeight: number = plotHeight - visualObj.viewModel.plotProperties.yAxis.start_padding;
const label_position: string = visualObj.viewModel.inputSettings.settings.label_options.label_position;
let y_offset: number = visualObj.viewModel.inputSettings.settings.label_options.label_y_offset;
let line_offset: number = visualObj.viewModel.inputSettings.settings.label_options.label_line_offset;
line_offset = label_position === "top" ? line_offset : -(line_offset + visualObj.viewModel.inputSettings.settings.label_options.label_size / 2);
const label_height: number = label_position === "top" ? (0 + y_offset) : (xAxisHeight - y_offset);
const theta: number = visualObj.viewModel.inputSettings.settings.label_options.label_angle_offset;
const max_line_length: number = visualObj.viewModel.inputSettings.settings.label_options.label_line_max_length;

/*
const calcOffsetFromAngle = (y: number) => {
const theta = 45;
const theta_2 = 180 - (90 + theta);
return Math.sin(theta) * y / Math.sin(theta_2);
}
*/

selection.select(".text-labels")
.selectAll(".text-group-inner")
.data(visualObj.viewModel.plotPoints)
.join(
(enter) => {
let grp = enter.append("g").classed("text-group-inner", true)
let label_options = visualObj.viewModel.inputSettings.settings.label_options;
console.log(label_options)

grp.append("text")
.text(d => d.label.text_value)
.attr("x", d => visualObj.viewModel.plotProperties.xScale(d.x))
.attr("y", 20)
.attr("x", d => {
const y: number = visualObj.viewModel.plotProperties.yScale(d.value);
let side_length: number = label_position === "top" ? (y - label_height) : (label_height - y);
side_length = Math.min(side_length, max_line_length);
const x: number = visualObj.viewModel.plotProperties.xScale(d.x);
const theta_2: number = 180 - (90 + theta);
return Math.sin(theta * Math.PI / 180) * side_length / Math.sin(theta_2 * Math.PI / 180) + x;
})
.attr("y", d => {
const y: number = visualObj.viewModel.plotProperties.yScale(d.value);
let side_length: number = label_position === "top" ? (y - label_height) : (label_height - y);
side_length = Math.min(side_length, max_line_length);
return y + (label_position === "top" ? -side_length : side_length);
})
.style("text-anchor", "middle")
.style("font-size", `${label_options.label_size}px`)
.style("font-family", label_options.label_font)
.style("fill", label_options.label_colour);

grp.append("line")
.attr("x1", d => visualObj.viewModel.plotProperties.xScale(d.x))
.attr("y1", 25)
.attr("x1", d => {
const y: number = visualObj.viewModel.plotProperties.yScale(d.value);
let side_length: number = label_position === "top" ? (y - label_height) : (label_height - y);
side_length = Math.min(side_length, max_line_length);
const x: number = visualObj.viewModel.plotProperties.xScale(d.x);
const theta_2: number = 180 - (90 + theta);
return Math.sin(theta * Math.PI / 180) * side_length / Math.sin(theta_2 * Math.PI / 180) + x;
})
.attr("y1", d => {
const y: number = visualObj.viewModel.plotProperties.yScale(d.value);
let side_length: number = label_position === "top" ? (y - label_height) : (label_height - y);
side_length = Math.min(side_length, max_line_length);
return y + (label_position === "top" ? -side_length : side_length) + line_offset;
})
.attr("x2", d => visualObj.viewModel.plotProperties.xScale(d.x))
.attr("y2", d => visualObj.viewModel.plotProperties.yScale(d.value))
.style("stroke", "black")
.style("stroke-width", d => (d.label.text_value ?? "") === "" ? 0 : 1);
.style("stroke", visualObj.viewModel.inputSettings.settings.label_options.label_line_colour)
.style("stroke-width", d => (d.label.text_value ?? "") === "" ? 0 : visualObj.viewModel.inputSettings.settings.label_options.label_line_width)
.style("stroke-dasharray", visualObj.viewModel.inputSettings.settings.label_options.label_line_type);

grp.call(d3.drag().on("drag", function(e) {
e.subject.label.x = e.sourceEvent.x;
Expand All @@ -38,7 +91,7 @@ export default function drawLabels(selection: svgBaseType, visualObj: Visual) {
d3.select(this)
.select("line")
.attr("x1", e.sourceEvent.x)
.attr("y1", e.sourceEvent.y + 5);
.attr("y1", e.sourceEvent.y + line_offset);
}));

return grp
Expand All @@ -47,19 +100,45 @@ export default function drawLabels(selection: svgBaseType, visualObj: Visual) {
let label_options = visualObj.viewModel.inputSettings.settings.label_options;
update.select("text")
.text(d => d.label.text_value)
.attr("x", d => d.label.x ?? visualObj.viewModel.plotProperties.xScale(d.x))
.attr("y", d => d.label.y ?? 20)
.attr("x", d => {
const y: number = visualObj.viewModel.plotProperties.yScale(d.value);
let side_length: number = label_position === "top" ? (y - label_height) : (label_height - y);
side_length = Math.min(side_length, max_line_length);
const x: number = visualObj.viewModel.plotProperties.xScale(d.x);
const theta_2: number = 180 - (90 + theta);
return d.label.x ?? Math.sin(theta * Math.PI / 180) * side_length / Math.sin(theta_2 * Math.PI / 180) + x;
})
.attr("y", d => {
const y: number = visualObj.viewModel.plotProperties.yScale(d.value);
let side_length: number = label_position === "top" ? (y - label_height) : (label_height - y);
side_length = Math.min(side_length, max_line_length);
return d.label.y ?? y + (label_position === "top" ? -side_length : side_length);
})
.style("text-anchor", "middle")
.style("font-size", `${label_options.label_size}px`)
.style("font-family", label_options.label_font)
.style("fill", label_options.label_colour);

update.select("line")
.attr("x1", d => d.label.x ?? visualObj.viewModel.plotProperties.xScale(d.x))
.attr("y1", d => (d.label.y ?? 20) + 5)
.attr("x1", d => {
const y: number = visualObj.viewModel.plotProperties.yScale(d.value);
let side_length: number = label_position === "top" ? (y - label_height) : (label_height - y);
side_length = Math.min(side_length, max_line_length);
const x: number = visualObj.viewModel.plotProperties.xScale(d.x);
const theta_2: number = 180 - (90 + theta);
return d.label.x ?? Math.sin(theta * Math.PI / 180) * side_length / Math.sin(theta_2 * Math.PI / 180) + x;
})
.attr("y1", d => {
const y: number = visualObj.viewModel.plotProperties.yScale(d.value);
let side_length: number = label_position === "top" ? (y - label_height) : (label_height - y);
side_length = Math.min(side_length, max_line_length);
return (d.label.y ?? y + (label_position === "top" ? -side_length : side_length)) + line_offset;
})
.attr("x2", d => visualObj.viewModel.plotProperties.xScale(d.x))
.attr("y2", d => visualObj.viewModel.plotProperties.yScale(d.value))
.style("stroke-width", d => (d.label.text_value ?? "") === "" ? 0 : 1);
.style("stroke", visualObj.viewModel.inputSettings.settings.label_options.label_line_colour)
.style("stroke-width", d => (d.label.text_value ?? "") === "" ? 0 : visualObj.viewModel.inputSettings.settings.label_options.label_line_width)
.style("stroke-dasharray", visualObj.viewModel.inputSettings.settings.label_options.label_line_type);

return update
}
Expand Down
1 change: 0 additions & 1 deletion src/D3 Plotting Functions/initialiseSVG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ export default function initialiseSVG(selection: svgBaseType,
selection.append('text').classed("yaxislabel", true)
selection.append('g').classed("linesgroup", true)
selection.append('g').classed("dotsgroup", true)
selection.append("g").classed("text-labels", true)
}
10 changes: 9 additions & 1 deletion src/defaultSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,17 @@ const defaultSettings = {
},
label_options: {
show_labels: { default: true },
label_position: { default: "top", valid: ["top", "bottom"] },
label_y_offset: { default: 20 },
label_line_offset: { default: 5 },
label_angle_offset: { default: 0, valid: { numberRange: { min: -90, max: 90 }}},
label_font: textOptions.font,
label_size: textOptions.size,
label_colour: colourOptions.standard
label_colour: colourOptions.standard,
label_line_colour: colourOptions.standard,
label_line_width: { default: 1, valid: lineOptions.width.valid },
label_line_type: { default: "10 0", valid: lineOptions.type.valid },
label_line_max_length: { default: 1000, valid: { numberRange: { min: 0, max: 10000 }}}
}
};

Expand Down
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"alwaysStrict": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictBindCallApply": true,
Expand Down

0 comments on commit f7821e9

Please sign in to comment.