UNPKG

@springernature/nn-charts

Version:
759 lines (667 loc) 34.9 kB
"use strict"; 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); // }); } }]); }();