Skip to content

Commit

Permalink
Merge pull request #144 from AUS-DOH-Safety-and-Quality/tooltip-line-…
Browse files Browse the repository at this point in the history
…refactor

Refactor plotting to central class
  • Loading branch information
andrjohns authored Jul 16, 2023
2 parents b083c09 + 952d9df commit 44522a8
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 325 deletions.
40 changes: 40 additions & 0 deletions src/Classes/plottingClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as d3 from "d3";
import powerbi from "powerbi-visuals-api";
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
import svgDotsClass from "./svgDotsClass";
import svgIconClass from "./svgIconClass";
import svgLinesClass from "./svgLinesClass";
import svgTooltipLineClass from "./svgTooltipLineClass";
import svgXAxisClass from "./svgXAxisClass";
import svgYAxisClass from "./svgYAxisClass";
import viewModelClass from "./viewModelClass";

class plottingClass {
svg: d3.Selection<SVGSVGElement, unknown, null, undefined>;
svgTooltipLine: svgTooltipLineClass;
svgXAxis: svgXAxisClass;
svgYAxis: svgYAxisClass;
svgDots: svgDotsClass;
svgLines: svgLinesClass;
svgIcons: svgIconClass;

draw(viewModel: viewModelClass): void {
this.svg.attr("width", viewModel.plotProperties.width)
.attr("height", viewModel.plotProperties.height);
Object.getOwnPropertyNames(this)
.filter(d => !(["draw", "svg"].includes(d)))
.forEach(key => this[key].draw(viewModel));
}

constructor(options: VisualConstructorOptions) {
this.svg = d3.select(options.element).append("svg");
this.svgTooltipLine = new svgTooltipLineClass(this.svg);
this.svgXAxis = new svgXAxisClass(this.svg);
this.svgYAxis = new svgYAxisClass(this.svg);
this.svgDots = new svgDotsClass(this.svg);
this.svgLines = new svgLinesClass(this.svg);
this.svgIcons = new svgIconClass(this.svg);
}
}

export default plottingClass;
10 changes: 4 additions & 6 deletions src/Classes/svgDotsClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ class svgDotsClass {

draw(viewModel: viewModelClass): void {
this.dotsGroup.selectAll(".dotsgroup").remove()
if (!(viewModel.plotPoints)) {
if (!(viewModel.plotProperties.displayPlot)) {
return;
}
const lower: number = viewModel.plotProperties.yAxis.lower;
const upper: number = viewModel.plotProperties.yAxis.upper;

this.dotsGroup
.append('g')
Expand All @@ -27,11 +29,7 @@ class svgDotsClass {
.attr("cx", (d: plotData) => viewModel.plotProperties.xScale(d.x))
.attr("r", (d: plotData) => d.aesthetics.size)
.style("fill", (d: plotData) => {
if (viewModel.plotProperties.displayPlot) {
return between(d.value, viewModel.plotProperties.yAxis.lower, viewModel.plotProperties.yAxis.upper) ? d.aesthetics.colour : "#FFFFFF";
} else {
return "#FFFFFF";
}
return between(d.value, lower, upper) ? d.aesthetics.colour : "#FFFFFF";
})
}

Expand Down
2 changes: 1 addition & 1 deletion src/Classes/svgIconClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class svgIconClass {
return iconsPresent;
}

drawIcons(viewModel: viewModelClass): void {
draw(viewModel: viewModelClass): void {
d3.selectAll(".icongroup").remove()
const draw_variation: boolean = viewModel.inputSettings.nhs_icons.show_variation_icons;
if (!draw_variation) {
Expand Down
23 changes: 0 additions & 23 deletions src/Classes/svgObjectClass.ts

This file was deleted.

32 changes: 0 additions & 32 deletions src/Classes/svgSelectionClass.ts

This file was deleted.

43 changes: 43 additions & 0 deletions src/Classes/svgTooltipLineClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as d3 from "d3";
import viewModelClass from "./viewModelClass";
type SelectionBase = d3.Selection<SVGGElement, unknown, null, undefined>;

class svgTooltipLineClass {
tooltipLineGroup: SelectionBase;

draw(viewModel: viewModelClass): void {
this.tooltipLineGroup.selectAll(".obs-sel").remove()
this.tooltipLineGroup.selectAll(".ttip-line").remove()
if (!(viewModel.plotProperties.displayPlot)) {
return;
}

this.tooltipLineGroup
.append('g')
.classed("obs-sel", true)
.selectAll(".obs-sel")
.data(viewModel.plotPoints)
.enter()
.append("rect")
.style("fill","transparent")
.attr("width", viewModel.plotProperties.width)
.attr("height", viewModel.plotProperties.height)

this.tooltipLineGroup
.append('g')
.classed("ttip-line", true)
.selectAll(".ttip-line")
.data(viewModel.plotPoints)
.enter()
.append("rect")
.attr("stroke-width", "1px")
.attr("width", ".5px")
.attr("height", viewModel.plotProperties.height)
.style("fill-opacity", 0)
}

constructor(svg: d3.Selection<SVGSVGElement, unknown, null, undefined>) {
this.tooltipLineGroup = svg.append("g");
}
}
export default svgTooltipLineClass
90 changes: 90 additions & 0 deletions src/Classes/svgXAxisClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as d3 from "d3";
import viewModelClass from "./viewModelClass";
import { axisProperties } from "./plotPropertiesClass";
type SelectionBase = d3.Selection<SVGGElement, unknown, null, undefined>;

class svgXAxisClass {
xAxisGroup: SelectionBase;
refreshingAxis: boolean

draw(viewModel: viewModelClass): void {
this.xAxisGroup.selectAll(".xaxisgroup").remove()
this.xAxisGroup.selectAll(".xaxislabel").remove()
if (!(viewModel.plotProperties.displayPlot)) {
return;
}

const xAxisProperties: axisProperties = viewModel.plotProperties.xAxis;
let xAxis: d3.Axis<d3.NumberValue>;

if (xAxisProperties.ticks) {
xAxis = d3.axisBottom(viewModel.plotProperties.xScale);
if (xAxisProperties.tick_count) {
xAxis.ticks(xAxisProperties.tick_count)
}
if (viewModel.tickLabels) {
xAxis.tickFormat(d => {
return viewModel.tickLabels.map(d => d.x).includes(<number>d)
? viewModel.tickLabels[<number>d].label
: "";
})
}
} else {
xAxis = d3.axisBottom(viewModel.plotProperties.xScale).tickValues([]);
}

const axisHeight: number = viewModel.plotProperties.height - viewModel.plotProperties.yAxis.end_padding;

this.xAxisGroup
.append('g')
.classed("xaxisgroup", true)
.call(xAxis)
.attr("color", xAxisProperties.colour)
// Plots the axis at the correct height
.attr("transform", "translate(0, " + axisHeight + ")")
.selectAll(".tick text")
// Right-align
.style("text-anchor", xAxisProperties.tick_rotation < 0.0 ? "end" : "start")
// Rotate tick labels
.attr("dx", xAxisProperties.tick_rotation < 0.0 ? "-.8em" : ".8em")
.attr("dy", xAxisProperties.tick_rotation < 0.0 ? "-.15em" : ".15em")
.attr("transform","rotate(" + xAxisProperties.tick_rotation + ")")
// Scale font
.style("font-size", xAxisProperties.tick_size)
.style("font-family", xAxisProperties.tick_font)
.style("fill", xAxisProperties.tick_colour);

const currNode: SVGGElement = this.xAxisGroup.selectAll(".xaxisgroup").selectChildren().node() as SVGGElement;
const xAxisCoordinates: DOMRect = currNode.getBoundingClientRect() as DOMRect;

// Update padding and re-draw axis if large tick values rendered outside of plot
const tickBelowPlotAmount: number = xAxisCoordinates.bottom - viewModel.plotProperties.height;
const tickLeftofPlotAmount: number = xAxisCoordinates.left;
if ((tickBelowPlotAmount > 0 || tickLeftofPlotAmount < 0)) {
if (!this.refreshingAxis) {
this.refreshingAxis = true
viewModel.plotProperties.yAxis.end_padding += tickBelowPlotAmount;
viewModel.plotProperties.initialiseScale();
this.draw(viewModel);
}
}
this.refreshingAxis = false

const bottomMidpoint: number = viewModel.plotProperties.height - (viewModel.plotProperties.height - xAxisCoordinates.bottom) / 2.5;
this.xAxisGroup
.append("text")
.classed("xaxislabel", true)
.attr("x",viewModel.plotProperties.width/2)
.attr("y", bottomMidpoint)
.style("text-anchor", "middle")
.text(xAxisProperties.label)
.style("font-size", xAxisProperties.label_size)
.style("font-family", xAxisProperties.label_font)
.style("fill", xAxisProperties.label_colour);
}

constructor(svg: d3.Selection<SVGSVGElement, unknown, null, undefined>) {
this.xAxisGroup = svg.append("g");
}
}
export default svgXAxisClass
75 changes: 75 additions & 0 deletions src/Classes/svgYAxisClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as d3 from "d3";
import viewModelClass from "./viewModelClass";
import { axisProperties } from "./plotPropertiesClass";
type SelectionBase = d3.Selection<SVGGElement, unknown, null, undefined>;

class svgYAxisClass {
yAxisGroup: SelectionBase;

draw(viewModel: viewModelClass): void {
this.yAxisGroup.selectAll(".yaxisgroup").remove()
this.yAxisGroup.selectAll(".yaxislabel").remove()
if (!(viewModel.plotProperties.displayPlot)) {
return;
}

const yAxisProperties: axisProperties = viewModel.plotProperties.yAxis;
let yAxis: d3.Axis<d3.NumberValue>;
const yaxis_sig_figs: number = viewModel.inputSettings.y_axis.ylimit_sig_figs;
const sig_figs: number = yaxis_sig_figs === null ? viewModel.inputSettings.spc.sig_figs : yaxis_sig_figs;
const multiplier: number = viewModel.inputSettings.spc.multiplier;

if (yAxisProperties.ticks) {
yAxis = d3.axisLeft(viewModel.plotProperties.yScale);
if (yAxisProperties.tick_count) {
yAxis.ticks(yAxisProperties.tick_count)
}
yAxis.tickFormat(
d => {
return viewModel.inputData.percentLabels
? (<number>d * (multiplier === 100 ? 1 : (multiplier === 1 ? 100 : multiplier))).toFixed(sig_figs) + "%"
: (<number>d).toFixed(sig_figs);
}
);
} else {
yAxis = d3.axisLeft(viewModel.plotProperties.yScale).tickValues([]);
}

this.yAxisGroup
.append('g')
.classed("yaxisgroup", true)
.call(yAxis)
.attr("color", yAxisProperties.colour)
.attr("transform", "translate(" + viewModel.plotProperties.xAxis.start_padding + ",0)")
.selectAll(".tick text")
// Right-align
.style("text-anchor", "right")
// Rotate tick labels
.attr("transform","rotate(" + yAxisProperties.tick_rotation + ")")
// Scale font
.style("font-size", yAxisProperties.tick_size)
.style("font-family", yAxisProperties.tick_font)
.style("fill", yAxisProperties.tick_colour);

const currNode: SVGGElement = this.yAxisGroup.selectAll(".yaxisgroup").selectChildren().node() as SVGGElement;
const yAxisCoordinates: DOMRect = currNode.getBoundingClientRect() as DOMRect;
const leftMidpoint: number = yAxisCoordinates.x * 0.7;

this.yAxisGroup
.append("text")
.classed("yaxislabel", true)
.attr("x",leftMidpoint)
.attr("y",viewModel.plotProperties.height/2)
.attr("transform","rotate(-90," + leftMidpoint +"," + viewModel.plotProperties.height/2 +")")
.text(yAxisProperties.label)
.style("text-anchor", "middle")
.style("font-size", yAxisProperties.label_size)
.style("font-family", yAxisProperties.label_font)
.style("fill", yAxisProperties.label_colour);
}

constructor(svg: d3.Selection<SVGSVGElement, unknown, null, undefined>) {
this.yAxisGroup = svg.append("g");
}
}
export default svgYAxisClass
Loading

0 comments on commit 44522a8

Please sign in to comment.