diff --git a/packages/malloy-render/src/component/render-result-metadata.ts b/packages/malloy-render/src/component/render-result-metadata.ts new file mode 100644 index 000000000..26e77f0ae --- /dev/null +++ b/packages/malloy-render/src/component/render-result-metadata.ts @@ -0,0 +1,100 @@ +/* + * Copyright 2023 Google LLC + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import {DataArray, Explore, Field, Result} from '@malloydata/malloy'; +import {valueIsNumber, valueIsString} from './util'; + +export interface FieldRenderMetadata { + field: Field; + min: number | null; + max: number | null; + minString: string | null; + maxString: string | null; +} + +export interface RenderResultMetadata { + fields: Record; +} + +export function getResultMetadata(result: Result) { + const fieldKeyMap: WeakMap = new WeakMap(); + const getFieldKey = (f: Field) => { + if (fieldKeyMap.has(f)) return fieldKeyMap.get(f)!; + const fieldKey = JSON.stringify(f.fieldPath); + fieldKeyMap.set(f, fieldKey); + return fieldKey; + }; + + const metadata: RenderResultMetadata = { + fields: {}, + }; + + function initFieldMeta(e: Explore) { + for (const f of e.allFields) { + if (f.isAtomicField()) { + const fieldKey = getFieldKey(f); + metadata.fields[fieldKey] = { + field: f, + min: null, + max: null, + minString: null, + maxString: null, + }; + } else if (f.isExploreField()) { + initFieldMeta(f); + } + } + } + + const populateFieldMeta = ( + data: DataArray, + metadata: RenderResultMetadata + ) => { + for (const row of data) { + for (const f of data.field.allFields) { + const value = f.isAtomicField() ? row.cell(f).value : undefined; + const fieldKey = getFieldKey(f); + const fieldMeta = metadata.fields[fieldKey]; + if (valueIsNumber(f, value)) { + const n = value; + fieldMeta.min = Math.min(fieldMeta.min ?? n, n); + fieldMeta.max = Math.max(fieldMeta.max ?? n, n); + } else if (valueIsString(f, value)) { + const s = value; + if (!fieldMeta.minString || fieldMeta.minString.length > s.length) + fieldMeta.minString = s; + if (!fieldMeta.maxString || fieldMeta.maxString.length < s.length) + fieldMeta.maxString = s; + } else if (f.isExploreField()) { + const data = row.cell(f) as DataArray; + populateFieldMeta(data, metadata); + } + } + } + }; + + initFieldMeta(result.data.field); + populateFieldMeta(result.data, metadata); + + return metadata; +} diff --git a/packages/malloy-render/src/component/render.ts b/packages/malloy-render/src/component/render.ts index 1418dec4b..1003c89b3 100644 --- a/packages/malloy-render/src/component/render.ts +++ b/packages/malloy-render/src/component/render.ts @@ -22,9 +22,15 @@ */ import {Result, Tag} from '@malloydata/malloy'; -import {LitElement, html, css} from 'lit'; +import {LitElement, html, css, PropertyValues} from 'lit'; import {customElement, property} from 'lit/decorators.js'; import './table'; +import {provide} from '@lit/context'; +import {resultContext} from './result-context'; +import { + RenderResultMetadata, + getResultMetadata, +} from './render-result-metadata'; // Get the first valid theme value or fallback to CSS variable function getThemeValue(prop: string, ...themes: Array) { @@ -57,8 +63,9 @@ export class MalloyRender extends LitElement { --malloy-theme--table-gutter-size: 15px; --malloy-theme--table-pinned-background: #f5fafc; --malloy-theme--table-pinned-border: 1px solid #daedf3; + --malloy-theme--font-family: Inter, system-ui, sans-serif; - font-family: Inter, system-ui, sans-serif; + font-family: var(--malloy-render--font-family); font-size: var(--malloy-render--table-font-size); } @@ -79,6 +86,15 @@ export class MalloyRender extends LitElement { @property({attribute: false}) result!: Result; + @provide({context: resultContext}) + metadata!: RenderResultMetadata; + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has('result')) { + this.metadata = getResultMetadata(this.result); + } + } + override render() { const modelTag = this.result.modelTag; const {tag: resultTag} = this.result.tagParse(); @@ -136,12 +152,14 @@ export class MalloyRender extends LitElement { localTheme, modelTheme ); + const fontFamily = getThemeValue('fontFamily', localTheme, modelTheme); const dynamicStyle = html`