UNPKG

@nteract/data-explorer

Version:
602 lines (593 loc) 26 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 __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DataExplorer = exports.default = void 0; const React = __importStar(require("react")); const grid_1 = __importDefault(require("../charts/grid")); const settings_1 = require("../charts/settings"); const settings_2 = require("../utilities/settings"); const VizControls_1 = __importDefault(require("./VizControls")); const HTMLLegend_1 = __importDefault(require("./HTMLLegend")); const FacetControls_1 = __importDefault(require("./FacetControls")); const Viz_1 = require("./Viz"); const Toolbar_1 = require("./Toolbar"); const styled_components_1 = __importDefault(require("styled-components")); const Dx = __importStar(require("../utilities/types")); const semiotic_1 = require("semiotic"); const d3_array_1 = require("d3-array"); const mediaType = "application/vnd.dataresource+json"; const generateChartKey = ({ view, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, trendLine, marginalGraphics, barGrouping, chart, }) => `${view}-${lineType}-${areaType}-${selectedDimensions.join(",")}-${selectedMetrics.join(",")}-${pieceType}-${summaryType}-${networkType}-${hierarchyType}-${trendLine}-${marginalGraphics}-${barGrouping}-${JSON.stringify(chart)}`; /* contour is an option for scatterplot pie is a transform on bar */ const defaultResponsiveSize = [440, 300]; const MetadataWarningWrapper = styled_components_1.default.div ` & { font-family: Source Sans Pro, Helvetica Neue, Helvetica, Arial, sans-serif; } `; const MetadataWarningContent = styled_components_1.default.div ` & { background: #cce; padding: 10px; padding-left: 20px; } `; const MetadataWarning = ({ metadata }) => { const warning = metadata && metadata.sampled ? (React.createElement("span", null, React.createElement("b", null, "NOTE:"), " This data is sampled")) : null; return (React.createElement(MetadataWarningWrapper, null, warning ? (React.createElement(MetadataWarningContent, null, warning)) : null)); }; const FlexWrapper = styled_components_1.default.div ` display: flex; flex-flow: row nowrap; width: 100%; `; const FacetWrapper = styled_components_1.default.div ` display: flex; flex-flow: wrap; min-width: 0; .responsive-container { flex: 1 0 calc(50% - 10px); margin: 5px; } `; const SemioticWrapper = styled_components_1.default.div ` width: 100%; .html-legend-item { color: var(--theme-app-fg); } .tick > path { stroke: lightgray; } .axis-labels, .ordinal-labels { fill: var(--theme-app-fg); font-size: 14px; } path.connector, path.connector-end { stroke: var(--theme-app-fg); } path.connector-end { fill: var(--theme-app-fg); } text.annotation-note-label, text.legend-title, .legend-item text { fill: var(--theme-app-fg); stroke: none; } .xyframe-area > path { stroke: var(--theme-app-fg); } .axis-baseline { stroke-opacity: 0.25; stroke: var(--theme-app-fg); } circle.frame-hover { fill: none; stroke: gray; } .rect { stroke: green; stroke-width: 5px; stroke-opacity: 0.5; } rect.selection { opacity: 0.5; } `; const processInitialData = (props, existingView, existingDX, filteredData) => { const { metadata, initialView, overrideSettings } = props; // Handle case of metadata being empty yet dx not set const dx = existingDX || metadata.dx || { chart: {}, facets: undefined }; const { chart = {}, facets } = dx, nonChartDXSettings = __rest(dx, ["chart", "facets"]); let { fields = [], primaryKey = [] } = props.data.schema; // Provide a default primaryKey if none provided if (primaryKey.length === 0) { primaryKey = [Dx.defaultPrimaryKey]; fields = [...fields, { name: Dx.defaultPrimaryKey, type: "integer" }]; } const dimensions = fields .filter((field) => field.type === "string" || field.type === "boolean" || field.type === "datetime") .map((field) => (Object.assign({ cardinality: 0, cardinalValues: [], unfilteredCardinality: 0, unfilteredCardinalValues: [] }, field))); // Should datetime data types be transformed into js dates before getting to this resource? const baseData = filteredData || props.data.data; const data = baseData.map((datapoint, datapointIndex) => { const mappedDatapoint = Object.assign({}, datapoint); fields.forEach((field) => { if (field.name === Dx.defaultPrimaryKey) { mappedDatapoint[Dx.defaultPrimaryKey] = datapointIndex; } if (field.type === "datetime") { mappedDatapoint[field.name] = new Date(mappedDatapoint[field.name]); } }); return mappedDatapoint; }); let largeDataset = true; let selectedDimensions = []; const metrics = fields .filter((field) => field.type === "integer" || field.type === "number" || field.type === "datetime") .filter((field) => !primaryKey.find((pkey) => pkey === field.name)) .map((d) => (Object.assign({}, d))); if (data.length < 5000) { largeDataset = false; } if (data.length < 100000) { metrics.forEach((m) => { if (!m.extent) { m.extent = d3_array_1.extent(data.map((d) => d[m.name])); } if (!m.unfilteredExtent) { if (m.type === "datetime") { m.unfilteredExtent = filteredData ? d3_array_1.extent(props.data.data.map((d) => new Date(d[m.name]))) : m.extent; } else { m.unfilteredExtent = filteredData ? d3_array_1.extent(props.data.data.map((d) => d[m.name])) : m.extent; } } }); dimensions.forEach((dim) => { if (dim.cardinality === 0) { const cardinalityHash = {}; cardinalityHash[dim.name] = {}; data.forEach((datapoint) => { const dimValue = datapoint[dim.name]; cardinalityHash[dim.name][dimValue] = true; }); const dimKeys = Object.keys(cardinalityHash[dim.name]); dim.cardinality = dimKeys.length; dim.cardinalValues = dimKeys; } if (dim.unfilteredCardinality === 0) { if (!filteredData) { dim.unfilteredCardinality = dim.cardinality; dim.unfilteredCardinalValues = dim.cardinalValues; } const cardinalityHash = {}; cardinalityHash[dim.name] = {}; props.data.data.forEach((datapoint) => { const dimValue = datapoint[dim.name]; cardinalityHash[dim.name][dimValue] = true; }); const dimKeys = Object.keys(cardinalityHash[dim.name]); dim.unfilteredCardinality = dimKeys.length; dim.unfilteredCardinalValues = dimKeys; } }); selectedDimensions = dimensions .sort((a, b) => a.cardinality - b.cardinality) .filter((data, index) => index === 0) .map((dim) => dim.name); } const finalChartSettings = Object.assign({ metric1: (metrics[0] && metrics[0].name) || "none", metric2: (metrics[1] && metrics[1].name) || "none", metric3: "none", metric4: "none", dim1: (dimensions[0] && dimensions[0].name) || "none", dim2: (dimensions[1] && dimensions[1].name) || "none", dim3: "none", timeseriesSort: "array-order", networkLabel: "none" }, chart); const displayChart = {}; let newState = { largeDataset, view: existingView || initialView, lineType: "line", areaType: "hexbin", trendLine: "none", marginalGraphics: "none", barGrouping: "Clustered", selectedDimensions, selectedMetrics: [], pieceType: "bar", summaryType: "violin", networkType: "force", hierarchyType: "dendrogram", dimensions, metrics, colors: settings_2.colors, // ui: {}, chart: finalChartSettings, overrideSettings, displayChart, primaryKey, data, editable: true, showLegend: true, facets, schema: props.data.schema, props, }; if (!filteredData) { newState = Object.assign(Object.assign({}, newState), nonChartDXSettings); } else { newState = Object.assign(Object.assign(Object.assign({}, newState), nonChartDXSettings), { metrics, dimensions, data }); } return newState; }; class DataExplorer extends React.PureComponent { constructor(props) { super(props); this.filterData = (filterFn, useBaseData) => { const _a = this.state, { data, props, view, metrics, dimensions } = _a, remainingState = __rest(_a, ["data", "props", "view", "metrics", "dimensions"]); if (!filterFn) { this.updateChart(processInitialData(props, view, remainingState)); return; } const { filteredData = data } = this.state; let newFilteredData = []; if (useBaseData) { newFilteredData = filterFn(props.data.data); } else { newFilteredData = filterFn(filteredData); } const newState = processInitialData(props, view, this.state, newFilteredData); this.updateChart(newState); }; this.updateChart = (updatedState) => { const { view, dimensions, metrics, chart, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, trendLine, marginalGraphics, barGrouping, colors, primaryKey, editable, showLegend, data: stateData, facets, overrideSettings, } = Object.assign(Object.assign({}, this.state), updatedState); if (!this.props.data && !this.props.metadata) { return; } let instantiatedView; const { data, height, OverrideVizControls, OverrideLegend, additionalViews, } = this.props; const chartKey = generateChartKey({ view, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, chart, trendLine, marginalGraphics, barGrouping, }); const extendedSettings = Object.assign(Object.assign({}, settings_1.semioticSettings), additionalViews); if (!view || view === "grid") { instantiatedView = (React.createElement(grid_1.default, Object.assign({}, this.props))); } else { const { Frame, chartGenerator } = extendedSettings[view]; const chartSettings = { metrics, dimensions, chart, colors, height, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, primaryKey, trendLine, marginalGraphics, barGrouping, setColor: this.setColor, filterData: this.filterData, showLegend, }; const baseFrameSettings = chartGenerator(stateData, data.schema, chartSettings); const { frameSettings } = baseFrameSettings; const frameOverride = typeof overrideSettings === "function" ? overrideSettings(chartSettings, baseFrameSettings, data, view) : overrideSettings; instantiatedView = (React.createElement(Frame, Object.assign({ responsiveWidth: true, size: defaultResponsiveSize }, frameSettings, frameOverride))); } let finalRenderedViz; if (facets && facets.length > 0) { let colorHashOverride; let colorDimOverride; const facetFrames = []; facets.forEach((baseDXSettings, facetIndex) => { const { dimFacet, initialView = view, data: facetDataSettings = this.state, metadata: facetMetadata = { dx: {} }, } = baseDXSettings; if (initialView === "grid") { const facetGridProps = Object.assign(Object.assign({}, this.props), baseDXSettings); facetFrames.push(React.createElement(grid_1.default, Object.assign({}, facetGridProps))); } else { const { dx: facetDX = {} } = facetMetadata; const { FacetFrame, chartGenerator: facetChartGenerator } = extendedSettings[initialView]; const { data: facetData, schema: facetSchema } = facetDataSettings; const filteredFacetData = dimFacet ? facetData.filter((d) => d[dimFacet.dim] === dimFacet.value) : facetData; const title = dimFacet ? `${dimFacet.dim}=${dimFacet.value}` : ""; const facetChartSettings = Object.assign({ metrics, dimensions, chart: Object.assign(Object.assign({}, chart), facetDX), colors, height, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, primaryKey, trendLine, marginalGraphics, barGrouping, setColor: this.setColor, filterData: this.filterData, showLegend }, facetDX); const facetFrameSettings = facetChartGenerator(filteredFacetData, facetSchema, facetChartSettings, colorHashOverride, colorDimOverride); const { colorHash, frameSettings, colorDim } = facetFrameSettings; colorHashOverride = colorHashOverride || colorHash; colorDimOverride = colorDimOverride || colorDim; const facetOverride = typeof overrideSettings === "function" ? overrideSettings(facetChartSettings, facetFrameSettings, facetDataSettings, view) : overrideSettings; facetFrames.push(React.createElement(FacetFrame, Object.assign({}, frameSettings, { beforeElements: React.createElement(FacetControls_1.default, { focusFunction: (dxSettings) => { this.updateChart({ chart: Object.assign(Object.assign({}, chart), dxSettings.dx), view: initialView, facets: [], }); }, removeFunction: (facetIndex) => { this.updateChart({ facets: facets.filter((d, i) => i !== facetIndex), }); }, dxSettings: facetMetadata, facetIndex: facetIndex }), size: defaultResponsiveSize, afterElements: null, margin: Object.assign(Object.assign({}, frameSettings.margin), { left: 55, right: 25, top: 25 }), title: title }, facetOverride))); } }); const ActualLegend = OverrideLegend ? OverrideLegend : HTMLLegend_1.default; finalRenderedViz = (React.createElement(FacetWrapper, null, React.createElement(semiotic_1.FacetController, null, facetFrames), React.createElement(ActualLegend, { valueHash: {}, colorHash: colorHashOverride, setColor: this.setColor, colors: colors }))); } else { const controlProps = { data: stateData, view, chart, metrics, dimensions, selectedDimensions, selectedMetrics, hierarchyType, summaryType, networkType, trendLine, marginalGraphics, barGrouping, lineType, areaType, setAreaType: this.setAreaType, updateChart: this.updateChart, updateDimensions: this.updateDimensions, setLineType: this.setLineType, updateMetrics: this.updateMetrics, generateFacets: this.generateFacets, filterData: this.filterData, setColor: this.setColor, }; const ActualVizControls = OverrideVizControls ? OverrideVizControls : VizControls_1.default; finalRenderedViz = (React.createElement(React.Fragment, null, instantiatedView, editable && React.createElement(ActualVizControls, Object.assign({}, controlProps)))); } const display = (React.createElement(SemioticWrapper, null, finalRenderedViz)); // If you pass an onMetadataChange function, then fire it and pass the updated dx settings so someone upstream can update the metadata or otherwise use it this.updateMetadata({ view, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, trendLine, marginalGraphics, barGrouping, colors, chart, }); this.setState((prevState) => { return Object.assign(Object.assign({}, updatedState), { displayChart: Object.assign(Object.assign({}, prevState.displayChart), { [chartKey]: display }) }); }); }; this.setView = (view) => { this.updateChart({ view }); }; this.updateMetadata = (overrideProps) => { const { onMetadataChange, metadata } = this.props; const { view, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, trendLine, marginalGraphics, barGrouping, colors, chart, facets, } = this.state; if (onMetadataChange) { onMetadataChange(Object.assign(Object.assign({}, metadata), { dx: Object.assign({ view, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, trendLine, marginalGraphics, barGrouping, colors, chart, facets }, overrideProps) }), mediaType); } }; this.setGrid = () => { this.updateChart({ view: "grid" }); }; this.setColor = (newColorArray) => { this.updateChart({ colors: newColorArray }); }; this.setLineType = (selectedLineType) => { this.updateChart({ lineType: selectedLineType }); }; this.setAreaType = (selectedAreaType) => { this.updateChart({ areaType: selectedAreaType }); }; this.updateDimensions = (selectedDimension) => { const oldDims = this.state.selectedDimensions; const newDimensions = oldDims.indexOf(selectedDimension) === -1 ? [...oldDims, selectedDimension] : oldDims.filter((dimension) => dimension !== selectedDimension); this.updateChart({ selectedDimensions: newDimensions }); }; this.updateMetrics = (selectedMetric) => { const oldMetrics = this.state.selectedMetrics; const newMetrics = oldMetrics.indexOf(selectedMetric) === -1 ? [...oldMetrics, selectedMetric] : oldMetrics.filter((metric) => metric !== selectedMetric); this.updateChart({ selectedMetrics: newMetrics }); }; this.generateFacets = (name) => (onWhat, which) => { if (onWhat === "metric") { const generatedFacets = this.state.metrics.map((metric) => { return { metadata: { dx: { [name]: metric.name, }, }, }; }); this.updateChart({ facets: generatedFacets }); } }; this.state = processInitialData(props); } componentDidMount() { this.updateChart(this.state); } render() { const { view, dimensions, metrics, chart, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, trendLine, marginalGraphics, barGrouping, largeDataset, facets, } = this.state; const { additionalViews } = this.props; let display = null; if (settings_1.semioticSettings[view] || view === "grid" || (additionalViews && additionalViews[view])) { const chartKey = generateChartKey({ view, lineType, areaType, selectedDimensions, selectedMetrics, pieceType, summaryType, networkType, hierarchyType, chart, trendLine, marginalGraphics, barGrouping, }); display = this.state.displayChart[chartKey]; } const toolbarProps = { dimensions, metrics, currentView: view, setGrid: this.setGrid, setView: this.setView, largeDataset, }; let children = React.Children.map(this.props.children, (child) => { if (!React.isValidElement(child)) { return; } const { componentType } = child.props; if (componentType === "viz") { const newProps = { children: display }; return React.cloneElement(child, newProps); } else if (componentType === "toolbar") { return React.cloneElement(child, toolbarProps); } return child; }); return (React.createElement("div", null, React.createElement(MetadataWarning, { metadata: this.props.metadata }), React.createElement(FlexWrapper, null, children ? (children) : (React.createElement(React.Fragment, null, React.createElement(Viz_1.Viz, null, display), (!facets || facets.length === 0) && (React.createElement(Toolbar_1.Toolbar, Object.assign({}, toolbarProps)))))))); } } exports.default = DataExplorer; exports.DataExplorer = DataExplorer; DataExplorer.MIMETYPE = mediaType; DataExplorer.defaultProps = { metadata: { dx: {}, }, height: 500, mediaType, initialView: "grid", overrideSettings: {}, additionalViews: {}, };