@springernature/nn-charts
Version:
Visualization for DAS products
759 lines (667 loc) • 34.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var d3 = _interopRequireWildcard(require("d3"));
var _config = require("./config");
var utils = _interopRequireWildcard(require("./utils"));
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var ScatterBubbleChart = exports["default"] = /*#__PURE__*/function () {
//names to be changed
function ScatterBubbleChart(data, legendsData, element, loadingStartedCb, loadingEndedCb, clickNode, hoverNode, hoverOutNode) {
_classCallCheck(this, ScatterBubbleChart);
this.loadingStartedCallback = loadingStartedCb;
this.loadingEndedCallback = loadingEndedCb;
//names to be changed
this.clickNodeCallback = clickNode;
this.hoverNodeCallback = hoverNode;
this.hoverNodeOutCallback = hoverOutNode;
this.srcData = data;
this.continentsLegendsData = legendsData;
_config.cf.legend.categoryLegend.legendCategoryColours = _config.cf.legend.categoryLegend.legendCategoryColours.slice(0, this.continentsLegendsData.length);
this.config = {
isLayerSelected: false,
// boolean var to trigger if a category layer is selected or not
isPreviousLayerSelected: false,
// boolean var to trigger if a category layer is selected or not
stickyActiveLabel: null /* acitvated if user has clicked on data rectangle to freeze layer highlighting */,
selectedLayer: null /* has a data layer/horizon been selected? if so, this will not be blank/null */,
data: data ? data : []
};
this.element = element;
}
return _createClass(ScatterBubbleChart, [{
key: "init",
value: function init() {
console.log("chart initiated");
// update broswer tab title from config.
//d3.selectAll(".chart__TabTitle").text(cf.chart__TabTitle);
// set the dimensions of the graph
utils.alertSize(); // get current browser window dimensions
// calculate/determine container width dimension
_config.cf.containerWidth = d3.selectAll(".content-panel.visualisation-panel").style("width").replaceAll("px", "");
// calculate chart dimensions
_config.cf.chartHeight = utils.setChartHeight(_config.cf);
_config.cf.chartWidth = utils.setChartWidth(_config.cf, "margins");
_config.cf.chartLegendWidth = _config.cf.chartWidth / 3;
// setting boolean value for .delaunayBoundaryLayer based on config.js value for .voronoiBoundaryLayer
// assumes both variables CANNOT be true
_config.cf.delaunayBoundaryLayer = _config.cf.voronoiBoundaryLayer !== true;
// re-storing src data as separate data obj.
_config.cf.data = this.srcData;
_config.cf.circleClickCallback = this.clickNodeCallback;
_config.cf.hoverNodeCallback = this.hoverNodeCallback;
_config.cf.hoverNodeOutCallback = this.hoverNodeOutCallback;
// call method to sort data in correct order (descending)
// add "ABS" to use the absolute value to determine data order (good for bubbles that show negative and positive values together)
_config.cf.sortedData = utils.sortData(_config.cf.data, "ABSdescending", _config.cf.circleRadiusAttributeField // TEST SORTING on "orgFieldCitationRatio"
);
// format indentifier field for each nodes
_config.cf.sortedData.forEach(function (node) {
node.nodeIdentierFormatted = node[_config.cf.dataObjectIdentifier].replaceAll(".", "_");
});
// cycle through data, adding additonal attribute information
// Needed for prototype. Likely not needed for 'Live'
_config.cf.sortedData.forEach(function (d, i) {
d.entityIdentifier = "id".concat(i); // create a proxy unique ID
d.value = d[_config.cf.dataObjectIdentifier];
d.id = d[_config.cf.dataObjectIdentifier]; // create a second proxy unique ID
d.text = d.institutionName;
// store top-most N of sorted data into seperate array to plot (N=50; set in config).
d.selected = i < _config.cf.displayTopNEntities;
});
// sort filteredData to build hierarchy chart in alphabetical order
// (may need to be changed based on agreed logic for displaying person names)
var alphabeticallySortedEntityData = _config.cf.data.sort(function (a, b) {
return utils.compareStrings(a[_config.cf.entitySelectionListSortOrder], b[_config.cf.entitySelectionListSortOrder]);
});
//
// OUT OF SCOPE FOR v1.0
//
// Initialize the Multi Select for Continent Selection
// Proxy multi selection list needed only for prototype
// NOT NEEDED IN PRODUCTIONISED SOLUTION. REPLACED WITH REACT EQUIVALENT
/*
new MultiSelect("#dynamicContinentList", {
data: cf.proxyContinentLU,
placeholder: "Continents",
search: false,
selectAll: false,
listAll: false,
onSelect: function (value, text, element) {
const index = cf.selectedContinents.indexOf(value);
if (index == -1) {
cf.selectedContinents.push(value);
}
},
onUnselect: function (value, text, element) {
const index = cf.selectedContinents.indexOf(value);
if (index != -1) {
cf.selectedContinents.splice(index, 1);
}
},
});
*/
// set eventlistener for clicking 'submit selection button
// Proxy needed only for prototype. NOT NEEDED IN PRODUCTIONIED VERSION
d3.selectAll(".submitSelections").on("click", function () {
utils.submit_selections(_config.cf.selectedContinents, _config.cf.selectedCountries, _config.cf, _config.cf.baseGroupForDataElements, xBottom, yLeft, createdColourPalette, _config.cf.dataPointBoundaries, _config.cf.fieldTitles, _config.cf.data);
});
//
// OUT OF SCOPE FOR v1.0
//
// Initialize the Multi Select for Entity Selection
// Proxy multi selection list needed only for prototype
// NOT NEEDED IN PRODUCTIONISED SOLUTION. REPLACED WITH REACT EQUIVALENT
//
// MAY NEED TO GRAB SOME OF CODE LOGIC IMPLEMENTED VIA 'onSelect' option
//
// new MultiSelect("#dynamicEntityList", {
// data: alphabeticallySortedEntityData,
// placeholder: "Select entities to compare",
// search: false,
// selectAll: false,
// listAll: false,
// max: cf.maxNSelectedEntities, // Maximum number of items that can be selected
// onSelect: function (value, text, element) {
// //
// console.log("dynamicEntityList onSelect:", value, text, element);
// //
// // appended selected entity "D" datum object onto array of selected entities.
// cf.selectedEntityData.push(
// cf.data.filter(function (d, i) {
// return d[cf.dataObjectIdentifier] == value;
// })[0]
// );
// // append selected entity data circles for updated array of selected entities.
// const appendedDataCircles = appendDataCircles(
// cf,
// cf.baseGroupForDataElements.classNames.join("."),
// cf.selectedEntityData,
// xBottom,
// yLeft,
// createdColourPalette
// );
// // append selected entity data objects.
// const appendedDataLabels = appendDataLabels(
// cf,
// svg,
// cf.selectedEntityData,
// xBottom,
// yLeft,
// createdColourPalette
// /* cf.currentTransform */
// );
// // delete current bounding box definition for 'selected entities' befor redefining new bounding attributes from new data.
// cf.dataPointBoundaries = deleteJSONArrayElement(
// cf.dataPointBoundaries,
// "type",
// "selectedEntities"
// );
// // get new boundary attribution for new set of 'selected entities'
// const newDataPointBoundaries = getDataPointBoundaries(
// cf.dataPointBoundaries,
// cf.selectedEntityData,
// cf.fieldTitles.x,
// cf.fieldTitles.y,
// "selectedEntities"
// );
// // update and dsiplay new bounding rects
// const appendedDataBoundingRects = appendDataBoundingRects(
// cf.dataPointBoundaries,
// cf.baseGroupForDataElements.classNames.join("."),
// xBottom,
// yLeft
// );
// // update delaunary for new entity selection set
// // remove old, and append new dealauany interaction layer
// const updatedDelaunayLayer = addDelaunayInteractionLayer(
// cf,
// xBottom,
// yLeft,
// cf.selectedEntityData,
// svg
// );
// // call and update chart zoom translation (needed to prevent chart panning from previous state)
// svg.call(zoom.transform, cf.currentTransform);
// },
// onUnselect: function (value, text, element) {
// //
// console.log("dynamicEntityList onUnselect:", value, text, element);
// //
// // delete entity data datum from array of selected entities based on value deselected
// cf.selectedEntityData = deleteJSONArrayElement(
// cf.selectedEntityData,
// cf.dataObjectIdentifier,
// value
// );
// // remove all entity node data circles and node data labels from chart view.
// d3.selectAll(".nodeContent.node__circle.node").remove();
// d3.selectAll(".nodeContentElement.nodeLabels").remove();
// // append selected entity data objects.
// const appendedDataCircles = appendDataCircles(
// cf,
// cf.baseGroupForDataElements.classNames.join("."),
// cf.selectedEntityData,
// xBottom,
// yLeft,
// createdColourPalette
// );
// // append selected entity data objects.
// const appendedDataLabels = appendDataLabels(
// cf,
// svg,
// cf.selectedEntityData,
// xBottom,
// yLeft,
// createdColourPalette
// );
// // delete current bounding box definition for 'selected entities' befor redefining new bounding attributes from new data.
// cf.dataPointBoundaries = deleteJSONArrayElement(
// cf.dataPointBoundaries,
// "type",
// "selectedEntities"
// );
// // get new boundary attribution for new set of 'selected entities'
// getDataPointBoundaries(
// cf.dataPointBoundaries,
// cf.selectedEntityData,
// cf.fieldTitles.x,
// cf.fieldTitles.y,
// "selectedEntities"
// );
// // update and dsiplay new bounding rects
// const appendedDataBoundingRects = appendDataBoundingRects(
// cf.dataPointBoundaries,
// cf.baseGroupForDataElements.classNames.join("."),
// xBottom,
// yLeft
// );
// // update delaunary for new entity selection set
// const updatedDelaunayLayer = addDelaunayInteractionLayer(
// cf,
// xBottom,
// yLeft,
// cf.selectedEntityData,
// svg
// );
// // call and update chart zoom translation (needed to prevent chart panning from previous state)
// svg.call(zoom.transform, cf.currentTransform);
// },
// });
//
// OUT OF SCOPE FOR v1.0
//
// populate proxy selection list to allow user to switch axes set used on chart
/* d3.select("#axisSwitchList")
.selectAll(".axisSwitchList-option")
.data(cf.axisSwitchSets)
.enter()
.append("option")
.attr("class", "axisSwitchList-option")
.attr("data-value", function (d, i) {
return d.value;
})
.attr("value", function (d, i) {
return d.value;
})
.text(function (d, i) {
return d.text;
}); */
// add eventlistener for axis switch selection list
// Proxy selection list needed only for prototype
// NOT NEEDED IN PRODUCTIONISED SOLUTION. REPLACED WITH REACT EQUIVALENT
//
// MAY NEED TO GRAB SOME OF CODE LOGIC IMPLEMENTED VIA 'onSelect' option
//
//
// OUT OF SCOPE FOR v1.0
//
// document
// .getElementById("axisSwitchList")
// .addEventListener("change", function () {
// let value = this.value;
// cf.quadrantLabels.axisSet = value;
// // update field defintions to change attribute definitions to use of chart zxes
// cf.fieldTitles.x = cf.axisSwitchSets[value].x;
// cf.fieldTitles.y = cf.axisSwitchSets[value].y;
// // delete current bounding box definition for 'all entities' befor redefining new bounding attributes from new data.
// cf.dataPointBoundaries = deleteJSONArrayElement(
// cf.dataPointBoundaries,
// "type",
// "allEntities"
// );
// // this needs to be replace with JSON array passed through API
// // Akarsh said he could/would provide it wjen productionized
// getDataPointBoundaries(
// cf.dataPointBoundaries,
// cf.data,
// cf.fieldTitles.x,
// cf.fieldTitles.y,
// "allEntities"
// );
// // delete current bounding box definition for 'selected entities' befor redefining new bounding attributes from new data.
// cf.dataPointBoundaries = deleteJSONArrayElement(
// cf.dataPointBoundaries,
// "type",
// "selectedEntities"
// );
// // this needs to be replace with JSON array passed through API
// // Akarsh said he could/would provide it wjen productionized
// getDataPointBoundaries(
// cf.dataPointBoundaries,
// cf.selectedEntityData,
// cf.fieldTitles.x,
// cf.fieldTitles.y,
// "selectedEntities"
// );
// // call method to transitio chart to new state
// // update position/dimensions/state of chart axis domains, chart titles, entity data circles, bounding rectangles
// const transitionedChart = transitionChart(
// cf.fieldTitles,
// xBottom,
// yLeft,
// cf.data,
// cf.chartTransitionEaseMethod,
// cf.chartTransitionDuration,
// cf.axis,
// cf.chartType,
// cf.axisTitlesLU
// );
// }); // end change selection
//
// OUT OF SCOPE FOR v1.0
//
// populate proxy selection list to allow user to change attribute for scaling node radius by
/* d3.select("#nodeScalingAttributeList")
.selectAll(".nodeScalingAttributeList-option")
.data(cf.nodeScalingAttributes)
.enter()
.append("option")
.attr("class", "nodeScalingAttributeList-option")
.attr("data-value", function (d, i) {
return d.value;
})
.attr("value", function (d, i) {
return d.value;
})
.text(function (d, i) {
return d.text;
}); */
//
// OUT OF SCOPE FOR v1.0
//
// add eventlistener for scaling node radius selection list
/* document
.getElementById("nodeScalingAttributeList")
.addEventListener("change", function () {
let value = this.value;
// update stored global value for scaling attributw
cf.circleRadiusAttributeField = cf.nodeScalingAttributes[value].r;
// determine maximum value of required scaling attribute (defined in config.js) based on selected data field
cf.maxScalingValue = determineMaximumValueOfRequiredScalingAttribute(cf);
// create circle radius scaling power law
cf.CircleRadiusScale = createCircleRadiusScalingLaw(cf);
// transition radius only of entity data circles (x- y- coordinates do not change)
d3.selectAll(".nodeContent.node__circle.node")
.transition()
.duration(cf.chartTransitionDuration)
.ease(cf.chartTransitionEaseMethod)
.attr("r", function (d, i) {
return cf.CircleRadiusScale(d[cf.circleRadiusAttributeField]);
});
// transition all data circle node labels
d3.selectAll(".nodeContentElement.nodeLabels")
.transition()
.duration(cf.chartTransitionDuration)
.ease(cf.chartTransitionEaseMethod)
.attr("x", function (d, i) {
d.plottedX = xBottom(d[cf.fieldTitles.x]);
let nodeRadius = cf.CircleRadiusScale(d[cf.circleRadiusAttributeField]);
return (
cf.currentTransform.x +
xBottom(d[cf.fieldTitles.x]) * cf.currentTransform.k +
nodeRadius
);
})
.attr("y", function (d) {
d.plottedY = yLeft(d[cf.fieldTitles.y]);
let nodeRadius = cf.CircleRadiusScale(d[cf.circleRadiusAttributeField]);
return (
cf.currentTransform.y +
yLeft(d[cf.fieldTitles.y]) * cf.currentTransform.k -
nodeRadius
);
});
return;
}); */
//
//
//
//
// NOW BUILD CHART
//
//
//
//
// this needs to be replace with JSON array passed through API
// Akarsh said he could/would provide it wjen productionized
utils.getDataPointBoundaries(_config.cf.dataPointBoundaries, _config.cf.data, _config.cf.fieldTitles.x, _config.cf.fieldTitles.y, "allEntities");
// populate array with default N top entities.
_config.cf.selectedEntityData = utils.getDefaultEntityData(_config.cf.sortedData, _config.cf.displayTopNEntities);
// this needs to be replace with JSON array passed through API
//Akarsh said he could/would provide it wjen productionized
utils.getDataPointBoundaries(_config.cf.dataPointBoundaries, _config.cf.selectedEntityData, _config.cf.fieldTitles.x, _config.cf.fieldTitles.y, "selectedEntities");
// determine data fields from ingested data
_config.cf.inputFieldsFromData = utils.getDataFieldColumns(this.srcData);
// call method to determine maximum value of required scaling attribute (defined in config.js) based on selected data field
_config.cf.maxScalingValue = utils.determineMaximumValueOfRequiredScalingAttribute(_config.cf);
// call method to create circle radius scaling power law
_config.cf.CircleRadiusScale = utils.createCircleRadiusScalingLaw(_config.cf);
// call method to create JSON of categoriesed data to allow group styling
_config.cf.createdCategorisedData = utils.createCategorisedData(_config.cf.sortedData, _config.cf.CategoryField);
// generate chart data specific colour palette
var createdColourPalette = utils.createColourPalette(this.continentsLegendsData, _config.cf.legend.categoryLegend.legendCategoryColours);
//
// OUT OF SCOPE FOR v1.0
//
// add eventlistener for user selection to draw own zoom area using brush selection and appending
// Needs migration over if fucntinmality is productionized
// document.querySelector("#brush").addEventListener("change", function () {
// //
// // get current check state of button
// cf.defineOwnZoomAreaCheckBoxState = this.checked;
// //
// // modfiy text on button
// d3.selectAll(".defineYourOwnZoomArea").text(function () {
// return cf.defineOwnZoomAreaCheckBoxState == true
// ? "Allow Chart Pan/Zoom"
// : "Define Your Own Zoom Area";
// });
// // call different method based on if check state is true or false.
// cf.defineOwnZoomAreaCheckBoxState == true
// ? start_brush_tool() /* Checkbox is checked */
// : end_brush_tool(); /* Checkbox is not checked */
// // call and update chart zoom translation (needed to prevent chart panning from previous state)
// svg.call(zoom.transform, cf.currentTransform);
// });
// add event listerner to ESC key to provide means to user to escape out of defining their own zoom area.
document.body.addEventListener("keydown", function (e) {
//
if (e.key === "Escape") {
svg.selectAll("g.brush").remove();
_config.cf.defineOwnZoomAreaCheckBoxState = false;
d3.selectAll(".defineYourOwnZoomArea").text("Define Your Own Zoom Area");
document.querySelector("#brush").checked = false;
}
});
// Set the zoom and Pan features:
// how much you can zoom, on which part,
// and what to do when there is a zoom
var zoom = d3.zoom().scaleExtent([1, 10])
// Infinite Pan
// .translateExtent([
// [-Infinity, -Infinity],
// [Infinity, Infinity],
// ])
//
// limited Pan Left and Down
.translateExtent([[-_config.cf.translateExtentBuffer, Number.NEGATIVE_INFINITY], [_config.cf.containerWidth + _config.cf.translateExtentBuffer, _config.cf.containerHeight + _config.cf.translateExtentBuffer / 2]]);
//
// OUT OF SCOPE FOR v1.0
//
// .on("zoom", function () {
// zoomhandler();
// });
// append main SVG panel to base container DIV for chart
var svg = d3.selectAll(".content-panel.visualisation-panel").append("svg").attr("class", _config.cf.svg__chart__container).attr("id", "svg__chart__container").attr("role", "presentation").attr("transform", "translate(".concat(_config.cf.svg__chart__container_position.x, ",").concat(_config.cf.svg__chart__container_position.y, ")")).attr("width", _config.cf.containerWidth - _config.cf.chartMargin.left - _config.cf.chartMargin.right).attr("height", _config.cf.chartHeight);
// append seconday SVG panel to base container DIV for chart legend elements
var legendsvg = d3.selectAll(".content-panel.visualisation-legend-panel").append("div").attr("class", "base-legend-panel svg__chart__legend__container row").attr("id", "svg__chart__legend__container");
// determine y-axis minimum and maximum values from data
// https://stackoverflow.com/questions/11488194/how-to-use-d3-min-and-d3-max-within-a-d3-json-command
var YAxisMin = utils.getMinimumDataValueFromKey(_config.cf.data, _config.cf.fieldTitles.y);
var YAxisMax = utils.getMaximumDataValueFromKey(_config.cf.data, _config.cf.fieldTitles.y);
// determine x-axis minimum and maximum values from data
// https://stackoverflow.com/questions/11488194/how-to-use-d3-min-and-d3-max-within-a-d3-json-command
var XAxisMin = utils.getMinimumDataValueFromKey(_config.cf.data, _config.cf.fieldTitles.x);
var XAxisMax = utils.getMaximumDataValueFromKey(_config.cf.data, _config.cf.fieldTitles.x);
// define domain and range of y axis
var yLeft = utils.createLinearYAxis([0, Math.ceil(YAxisMax / _config.cf.axis.y.axisRounding) * _config.cf.axis.y.axisRounding], [_config.cf.chartHeight - _config.cf.chartMargin.bottom - _config.cf.chartMargin.top, _config.cf.chartMargin.top]);
// define domain and range of y axis
// define new linear x-axis
var xBottom = utils.createLinearXAxis([Math.floor(XAxisMin / _config.cf.axis.x.axisRounding) * _config.cf.axis.x.axisRounding, Math.ceil(XAxisMax / _config.cf.axis.x.axisRounding) * _config.cf.axis.x.axisRounding], [_config.cf.chartMargin.left, _config.cf.chartWidth - _config.cf.chartMargin.right]);
// position new y-axis
var yAxis = utils.positionAxis(yLeft, _config.cf.axis.y, _config.cf.chartType);
// position new x-axis
var xAxis = utils.positionAxis(xBottom, _config.cf.axis.x, _config.cf.chartType);
// append new y-axis
var gY = utils.appendAxis(yAxis, _config.cf.axis.y, _config.cf.svg__chart__container, 0 /* y */, _config.cf.chartMargin.left /* x */);
// append new x-axis
var gX = utils.appendAxis(xAxis, _config.cf.axis.x, _config.cf.svg__chart__container, _config.cf.chartHeight - _config.cf.chartMargin.bottom - _config.cf.chartMargin.top /* y */, 0 /* x */);
// store axis domains as seperate variables to allow access elsewhere in code.
var x0 = xBottom.domain();
_config.cf.currentxAxisDomain = x0;
var y0 = yLeft.domain();
_config.cf.currentyAxisDomain = y0;
// modify axis tick classname declarations too allow appending of chart width grid lines
var yAxisTickClasses = utils.modifyAxisTickClasses(_config.cf.axis, "y", _config.cf.axis.y.axisTickFrequency, _config.cf.chartType);
// modify axis tick classname declarations too allow appending of chart height grid lines
var xAxisTickClasses = utils.modifyAxisTickClasses(_config.cf.axis, "x", _config.cf.axis.x.axisTickFrequency, _config.cf.chartType);
// append vertical gridlines to x axis
var appendedXAxisGridLines = utils.appendXAxisGridLines(-(d3.max(yLeft.range()) - d3.min(yLeft.range())), _config.cf.axis.x.gridLines, _config.cf.axis.x.classNames);
// // append y-axis title
var appendedYAxisTitle = utils.appendAxisTitle(_config.cf.svg__chart__container, d3.max(yLeft.range()), 40, _config.cf.axis, "y", _config.cf.axisTitlesLU, _config.cf.fieldTitles.y, "Left");
// // append x-axis title
var appendedXAxisTitle = utils.appendAxisTitle(_config.cf.axis.x.classNames.join("."), d3.max(xBottom.range()), 50, _config.cf.axis, "x", _config.cf.axisTitlesLU, _config.cf.fieldTitles.x, "");
// append full chart-width gridlines to y-axis.
var appendedYAxisGridLines = utils.appendYAxisGridLines(xBottom.range()[1] - xBottom.range()[0], _config.cf.axis.y.gridLines, _config.cf.axis.y.classNames);
// append new dealauany interaction layer
var updatedDelaunayLayer = utils.addDelaunayInteractionLayer(_config.cf, xBottom, yLeft, _config.cf.selectedEntityData, svg);
// append new g element to attach all chart space content to (rects, data circles, reference lines)
var group__chart__content = svg.append("g").attr("class", "group__chart__content").attr("clip-path", "url(#clip)");
// Add a clipPath: everything out of this area won't be drawn. var clip = svg
var clip = svg.append("defs").append("SVG:clipPath").attr("id", "clip").append("SVG:rect").attr("id", "clipRect").attr("x", _config.cf.chartMargin.left).attr("y", _config.cf.chartMargin.top).attr("width", d3.max(xBottom.range()) - _config.cf.chartMargin.left).attr("height", d3.max(yLeft.range()) - d3.min(yLeft.range()));
// append passive frame rectangle to chart space to help delineate chart space from surrounding
var appendedChartFrame = svg.append("rect").attr("class", "svg__rect__chart__frame").attr("x", _config.cf.chartMargin.left).attr("y", _config.cf.chartMargin.top).attr("width", d3.max(xBottom.range()) - _config.cf.chartMargin.left).attr("height", d3.max(yLeft.range()) - d3.min(yLeft.range()));
// append proxy thick black line to x and y axis at x/y=0
// Note this has to be positioning at where x-axis scale is x/y=0,
// MUST BE ADDED AFTER ANY DATA OBJECTS THAT TRANSGRESS THE X/Y=0 lines
// SO IT DOES NOT GET ASKED BY DATA OBJECTS
var appendedXAxisThickLine = utils.appendAxisThickLine(_config.cf.baseGroupForDataElements.classNames.join("."), yLeft, "x", xBottom);
var appendedYAxisThickLine = utils.appendAxisThickLine(_config.cf.baseGroupForDataElements.classNames.join("."), yLeft, "y", xBottom);
// determine max data value for y-axis attr.
var maxYAxis = utils.getMaximumDataValueFromKey(_config.cf.selectedEntityData, "orgTotalCitations");
// determine max data value for x-axis attr.
var maxXAxis = utils.getMaximumDataValueFromKey(_config.cf.selectedEntityData, "articlesCount");
// store locally current D3/zoom transform (i.e. 0,0,1)
_config.cf.currentTransform = d3.zoomIdentity;
//
// OUT OF SCOPE FOR v1.0
//
// append bounding rects
/*
const appendedDataBoundingRects = appendDataBoundingRects(
cf.dataPointBoundaries,
cf.baseGroupForDataElements.classNames.join("."),
xBottom,
yLeft
);
*/
//
// OUT OF SCOPE FOR v1.0
//
// append Y axis reference line ...
/*
const appendedYAxisReferenceLines = appendAxisReferenceLines(
cf.dataPointBoundaries,
cf.baseGroupForDataElements.classNames.join("."),
xBottom,
yLeft,
"y",
cf.axis.y.referenceLines
);
*/
//
// OUT OF SCOPE FOR v1.0
//
// append X axis reference line ...
/*
const appendedXAxisReferenceLines = appendAxisReferenceLines(
cf.dataPointBoundaries,
cf.baseGroupForDataElements.classNames.join("."),
xBottom,
yLeft,
"x",
cf.axis.x.referenceLines
);
*/
//
// OUT OF SCOPE FOR v1.0
//
// append quadrant description labels to chart.
/*
const appendedQuadrantLabels = appendQuadrantLabels(
cf.baseGroupForDataElements.classNames.join("."),
cf.quadrantLabels.labels[cf.quadrantLabels.axisSet],
xBottom,
yLeft,
cf.dataPointBoundaries.filter(function (d, i) {
return d.type == "allEntities";
})[0],
cf.quadrantLabels.verticalOffset
);
*/
// append selected entity data objects.
var appendedDataCircles = utils.appendDataCircles(_config.cf, svg, _config.cf.selectedEntityData, xBottom, yLeft, createdColourPalette, _config.cf.currentTransform);
// append selected entity data objects.
var appendedDataLabels = utils.appendDataLabels(_config.cf, svg, _config.cf.selectedEntityData, xBottom, yLeft, createdColourPalette, _config.cf.currentTransform);
// store full axis defintions as seperate variables to allow access elsewhere in code.
// append zoom defintion to base SVG
var SVG = svg.call(zoom);
// append mock zoom button
// nonly needed for prototype
var appendedMockZoomControlButtons = utils.appendMockZoomControlButtons("base-legend-panel", _config.cf.chartMargin.left, Number(_config.cf.legend.gapToLegends.y), _config.cf.chartMargin);
if (_config.cf.legend.categoryLegend.hasLegend) {
var appendedCategoryLegend = utils.appendCategoryLegend(
// base SVG panel
"base-legend-panel",
// text label info
{
text: _config.cf.legend.categoryLegend.text.text,
x: _config.cf.legend.categoryLegend.text.x,
y: _config.cf.legend.categoryLegend.text.y,
categoryVerticalSpacing: _config.cf.legend.categoryLegend.text.categoryVerticalSpacing
},
// category information
createdColourPalette);
}
if (_config.cf.legend.scaleLegend.hasLegend) {
var appendedScaleLegend = utils.appendScaleLegend(
// base SVG panel
"base-legend-panel");
}
// add event listenres for display/hide boundaing rectangles.
d3.selectAll(".display_bounding_rects").on("click", function () {
this.value = this.value === "true" ? "false" : "true";
var btnValue = this.value;
d3.selectAll(".framing__context_element").style("visibility", function () {
return btnValue === "true" ? "visible" : "hidden";
});
d3.select(this).text(function () {
// biome-ignore lint/suspicious/noDoubleEquals: <explanation>
return this.value == "true" ? "Hide Bounding Rectangles" : "Show Bounding Rectangles";
});
});
//
// window resize event listener
_config.cf.window = window.addEventListener("resize", function () {
utils.scatterBubbleOnWindowResize(_config.cf, xBottom, yLeft, _config.cf.selectedEntityData, gX, gY, svg);
});
//
// zoom-in event listener
// d3.selectAll(".zoom-in").on("click", function () {
// zoom.scaleBy(svg.transition().duration(50), 1.25);
// });
// //
// // zoom-out event listener
// d3.selectAll(".zoom-out").on("click", function () {
// zoom.scaleBy(svg.transition().duration(50), 1 / 1.25);
// });
// //
// // zoom-reset chart event listener
// d3.selectAll(".zoom-reset").on("click", function () {
// svg
// .transition()
// .duration(cf.chartTransitionDuration)
// .call(zoom.transform, d3.zoomIdentity);
// });
}
}]);
}();