UNPKG

@nteract/data-explorer

Version:
331 lines (330 loc) 15 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.semioticXYPlot = exports.semioticScatterplot = exports.semioticHexbin = void 0; const d3_scale_1 = require("d3-scale"); const React = __importStar(require("react")); const semiotic_1 = require("semiotic"); const HTMLLegend_1 = __importDefault(require("../components/HTMLLegend")); const tooltip_content_1 = __importDefault(require("../utilities/tooltip-content")); const utilities_1 = require("../utilities/utilities"); const shared_1 = require("./shared"); const styled_components_1 = __importDefault(require("styled-components")); const TooltipHeader = styled_components_1.default.div ` font-size: 14px; text-transform: uppercase; margin: 5px; font-weight: 900; `; const TooltipP = styled_components_1.default.div ` fontsize: 12px; texttransform: uppercase; margin: 5px; `; const binHash = { heatmap: semiotic_1.heatmapping, hexbin: semiotic_1.hexbinning, }; const steps = ["none", "#FBEEEC", "#f3c8c2", "#e39787", "#ce6751", "#b3331d"]; const thresholds = d3_scale_1.scaleThreshold() .domain([0.01, 0.2, 0.4, 0.6, 0.8]) .range(steps); function combineTopAnnotations(topQ, topSecondQ, dim2) { const combinedAnnotations = []; const combinedHash = {}; [...topQ, ...topSecondQ].forEach((topDatapoint) => { const hashD = combinedHash[topDatapoint[dim2]]; if (hashD) { const newCoordinates = (hashD.coordinates && [ ...hashD.coordinates, topDatapoint, ]) || [topDatapoint, hashD]; Object.keys(combinedHash[topDatapoint[dim2]]).forEach((key) => { delete combinedHash[topDatapoint[dim2]][key]; }); combinedHash[topDatapoint[dim2]].id = topDatapoint[dim2]; combinedHash[topDatapoint[dim2]].label = topDatapoint[dim2]; combinedHash[topDatapoint[dim2]].type = "react-annotation"; combinedHash[topDatapoint[dim2]].coordinates = newCoordinates; } else { combinedHash[topDatapoint[dim2]] = Object.assign({ type: "react-annotation", label: topDatapoint[dim2], id: topDatapoint[dim2], coordinates: [] }, topDatapoint); combinedAnnotations.push(combinedHash[topDatapoint[dim2]]); } }); return combinedAnnotations; } exports.semioticHexbin = (data, schema, options, colorHashOverride, colorDimOverride) => { return exports.semioticXYPlot(data, schema, options, options.areaType, colorHashOverride, colorDimOverride); }; exports.semioticScatterplot = (data, schema, options, colorHashOverride, colorDimOverride) => { return exports.semioticXYPlot(data, schema, options, "scatterplot", colorHashOverride, colorDimOverride); }; exports.semioticXYPlot = (data, schema, options, type = "scatterplot", colorHashOverride, colorDimOverride) => { const height = options.height - 150 || 500; const { chart, primaryKey, colors, setColor, dimensions, trendLine, marginalGraphics, } = options; const { dim1, dim2, dim3, metric1, metric2, metric3 } = chart; const filteredData = data.filter((datapoint) => datapoint[metric1] && datapoint[metric2] && (!metric3 || metric3 === "none" || datapoint[metric3])); const pointTooltip = (hoveredDatapoint) => { return (React.createElement(tooltip_content_1.default, { x: hoveredDatapoint.x, y: hoveredDatapoint.y }, React.createElement("h3", null, primaryKey.map((pkey) => hoveredDatapoint[pkey]).join(", ")), dimensions.map((dim) => (React.createElement("p", { key: `tooltip-dim-${dim.name}` }, dim.name, ":", " ", (hoveredDatapoint[dim.name].toString && hoveredDatapoint[dim.name].toString()) || hoveredDatapoint[dim.name]))), React.createElement("p", null, metric1, ": ", hoveredDatapoint[metric1]), React.createElement("p", null, metric2, ": ", hoveredDatapoint[metric2]), metric3 && metric3 !== "none" && (React.createElement("p", null, metric3, ": ", hoveredDatapoint[metric3])))); }; const areaTooltip = (hoveredDatapoint) => { const binItems = hoveredDatapoint.binItems || hoveredDatapoint.data || []; if (binItems.length === 0) { return null; } return (React.createElement(tooltip_content_1.default, { x: hoveredDatapoint.x, y: hoveredDatapoint.y }, React.createElement(TooltipHeader, null, "ID, ", metric1, ", ", metric2), binItems.map((binnedDatapoint, index) => { const id = dimensions .map((dim) => (binnedDatapoint[dim.name].toString && binnedDatapoint[dim.name].toString()) || binnedDatapoint[dim.name]) .join(","); return (React.createElement(TooltipP, { key: id + index }, id, ", ", binnedDatapoint[metric1], ", ", binnedDatapoint[metric2])); }))); }; let sizeScale = () => 5; const colorHash = colorHashOverride || { Other: "grey" }; const additionalSettings = {}; let annotations; if (dim2 && dim2 !== "none") { const topQ = [...filteredData] .sort((datapointA, datapointB) => datapointB[metric1] - datapointA[metric1]) .filter((d, index) => index < 3); const topSecondQ = [...filteredData] .sort((datapointA, datapointB) => datapointB[metric2] - datapointA[metric2]) .filter((datapoint) => topQ.indexOf(datapoint) === -1) .filter((d, index) => index < 3); annotations = combineTopAnnotations(topQ, topSecondQ, dim2); } // disabling annotations for now annotations = undefined; if (metric3 && metric3 !== "none") { const dataMin = Math.min(...filteredData.map((datapoint) => datapoint[metric3])); const dataMax = Math.max(...filteredData.map((datapoint) => datapoint[metric3])); sizeScale = d3_scale_1.scaleLinear().domain([dataMin, dataMax]).range([2, 20]); } const sortedData = shared_1.sortByOrdinalRange(metric1, (metric3 !== "none" && metric3) || metric2, "none", data); if ((type === "scatterplot" || type === "contour") && dim1 && dim1 !== "none") { const uniqueValues = shared_1.getUniqueValues(sortedData, dim1); if (!colorHashOverride) { uniqueValues.sort().forEach((dimValue, index) => { colorHash[dimValue] = index > 18 ? "grey" : colors[index % colors.length]; }); } additionalSettings.afterElements = (React.createElement(HTMLLegend_1.default, { valueHash: {}, colorHash: colorHash, setColor: setColor, colors: colors })); } let areas = []; if (type === "heatmap" || type === "hexbin" || (type === "contour" && dim3 === "none")) { areas = [{ coordinates: filteredData }]; if (type !== "contour") { const calculatedAreas = binHash[type]({ summaryType: { type, bins: 10 }, data: { coordinates: filteredData.map((datapoint) => (Object.assign(Object.assign({}, datapoint), { x: datapoint[metric1], y: datapoint[metric2] }))), }, size: [height, height], }); areas = calculatedAreas; const thresholdSteps = [0.2, 0.4, 0.6, 0.8, 1] .map((thresholdValue) => Math.floor(calculatedAreas.binMax * thresholdValue)) .reduce((thresholdArray, thresholdValue) => thresholdValue === 0 || thresholdArray.indexOf(thresholdValue) !== -1 ? thresholdArray : [...thresholdArray, thresholdValue], []); const withZeroThresholdSteps = [0, ...thresholdSteps]; const hexValues = []; withZeroThresholdSteps.forEach((thresholdValue, index) => { const nextValue = withZeroThresholdSteps[index + 1]; if (nextValue) { hexValues.push(`${thresholdValue + 1} - ${nextValue}`); } }); const thresholdColors = [ "#FBEEEC", "#f3c8c2", "#e39787", "#ce6751", "#b3331d", ]; const hexHash = {}; hexValues.forEach((binLabel, index) => { hexHash[binLabel] = thresholdColors[index]; }); thresholds .domain([0.01, ...thresholdSteps]) .range([ "none", ...thresholdColors.filter((d, index) => index < thresholdSteps.length), ]); additionalSettings.afterElements = (React.createElement(HTMLLegend_1.default, { valueHash: {}, values: hexValues, colorHash: hexHash, colors: colors, setColor: setColor })); } } else if (type === "contour") { const multiclassHash = {}; areas = []; filteredData.forEach((datapoint) => { if (!multiclassHash[datapoint[dim1]]) { multiclassHash[datapoint[dim1]] = { label: datapoint[dim1], color: colorHash[datapoint[dim1]], coordinates: [], }; areas.push(multiclassHash[datapoint[dim1]]); } multiclassHash[datapoint[dim1]].coordinates.push(datapoint); }); } const renderInCanvas = (type === "scatterplot" || type === "contour") && data.length > 999; let marginalGraphicsAxes = []; if (marginalGraphics !== "none" && type === "scatterplot") { marginalGraphicsAxes = [ { orient: "right", tickLineGenerator: () => React.createElement("g", null), tickFormat: () => "", marginalSummaryType: { type: marginalGraphics, showPoints: !renderInCanvas, }, }, { orient: "top", tickLineGenerator: () => React.createElement("g", null), tickFormat: () => "", marginalSummaryType: { type: marginalGraphics, showPoints: !renderInCanvas, }, }, ]; } let calculatedSummaryType; if (type === "scatterplot" && trendLine !== "none") { calculatedSummaryType = { type: "trendline", regressionType: trendLine }; } else if (type !== "scatterplot") { calculatedSummaryType = { type, bins: 10, thresholds: dim3 === "none" ? 6 : 3, }; } const xyPlotSettings = Object.assign({ xAccessor: type === "hexbin" || type === "heatmap" ? "x" : metric1, yAccessor: type === "hexbin" || type === "heatmap" ? "y" : metric2, axes: [ { orient: "left", ticks: 6, label: metric2, tickFormat: utilities_1.numeralFormatting, baseline: type === "scatterplot", tickSize: type === "heatmap" ? 0 : undefined, }, { orient: "bottom", ticks: 6, label: metric1, tickFormat: utilities_1.numeralFormatting, footer: type === "heatmap", baseline: type === "scatterplot", tickSize: type === "heatmap" ? 0 : undefined, }, ...marginalGraphicsAxes, ], points: (type === "scatterplot" || type === "contour") && data, canvasPoints: renderInCanvas, summaryType: calculatedSummaryType, summaryStyle: (areaDatapoint) => { if (type === "scatterplot") { return { stroke: "darkred", strokeWidth: 2, fill: "none" }; } return { fill: type === "contour" ? "none" : thresholds((areaDatapoint.binItems || areaDatapoint.data).length), stroke: type !== "contour" ? undefined : dim3 === "none" ? "#BBB" : areaDatapoint.parentSummary.color, strokeWidth: type === "contour" ? 2 : 1, }; }, pointStyle: (datapoint) => { return { r: renderInCanvas ? 2 : type === "contour" ? 3 : `${sizeScale(datapoint[metric3])}px`, fill: colorHash[datapoint[colorDimOverride || dim1]] || "black", fillOpacity: 0.75, stroke: renderInCanvas ? "none" : type === "contour" ? "white" : "black", strokeWidth: type === "contour" ? 0.5 : 1, strokeOpacity: 0.9, }; }, hoverAnnotation: true, responsiveWidth: false, size: [height + 105, height + 80], margin: { left: 75, bottom: 75, right: 30, top: 30 }, annotations: (type === "scatterplot" && annotations) || undefined, annotationSettings: { layout: { type: "marginalia", orient: "right", marginOffset: 30 }, }, tooltipContent: ((type === "hexbin" || type === "heatmap") && areaTooltip) || pointTooltip }, additionalSettings); if (type !== "scatterplot") { xyPlotSettings.summaries = areas; } return { frameSettings: xyPlotSettings, colorDim: dim1, colorHash }; };