Skip to content

Commit

Permalink
feat: drilling, style inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
skokenes committed Dec 6, 2024
1 parent 67dfdc4 commit 80faadd
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 119 deletions.
47 changes: 33 additions & 14 deletions packages/malloy-render/src/component/apply-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {renderList} from './render-list';
import {renderImage} from './render-image';
import {Dashboard} from './dashboard/dashboard';
import {LegacyChart} from './legacy-charts/legacy_chart';
import {hasAny} from './tag-utils';
import {renderTime} from './render-time';

export type RendererProps = {
Expand All @@ -36,27 +35,47 @@ export type RendererProps = {
customProps?: Record<string, Record<string, unknown>>;
};

const RENDER_TAG_LIST = [
'link',
'image',
'cell',
'list',
'list_detail',
'bar_chart',
'line_chart',
'dashboard',
'scatter_chart',
'shape_map',
'segment_map',
];

const CHART_TAG_LIST = ['bar_chart', 'line_chart'];

export function shouldRenderChartAs(tag: Tag) {
const tagNamesInOrder = Object.keys(tag.properties ?? {}).reverse();
return tagNamesInOrder.find(name => CHART_TAG_LIST.includes(name));
}

export function shouldRenderAs(f: Field | Explore, tagOverride?: Tag) {
const tag = tagOverride ?? f.tagParse().tag;
if (!f.isExplore() && f.isAtomicField()) {
if (tag.has('link')) return 'link';
if (tag.has('image')) return 'image';
return 'cell';
const tagNamesInOrder = Object.keys(tag.properties ?? {}).reverse();
for (const tagName of tagNamesInOrder) {
if (RENDER_TAG_LIST.includes(tagName)) {
if (['list', 'list_detail'].includes(tagName)) return 'list';
if (['bar_chart', 'line_chart'].includes(tagName)) return 'chart';
return tagName;
}
}
if (hasAny(tag, 'list', 'list_detail')) return 'list';
if (hasAny(tag, 'bar_chart', 'line_chart')) return 'chart';
if (tag.has('dashboard')) return 'dashboard';
if (hasAny(tag, 'scatter_chart')) return 'scatter_chart';
if (hasAny(tag, 'shape_map')) return 'shape_map';
if (hasAny(tag, 'segment_map')) return 'segment_map';
else return 'table';

if (!f.isExplore() && f.isAtomicField()) return 'cell';
return 'table';
}

export const NULL_SYMBOL = '∅';

export function applyRenderer(props: RendererProps) {
const {field, dataColumn, resultMetadata, tag, customProps = {}} = props;
const renderAs = shouldRenderAs(field, tag);
const {field, dataColumn, resultMetadata, customProps = {}} = props;
const renderAs = resultMetadata.field(field).renderAs;
let renderValue: JSXElement = '';
const propsToPass = customProps[renderAs] || {};
if (dataColumn.isNull()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default function registerWebComponent({
modelDef: undefined,
scrollEl: undefined,
onClick: undefined,
onDrill: undefined,
vegaConfigOverride: undefined,
tableConfig: undefined,
dashboardConfig: undefined,
Expand Down
30 changes: 24 additions & 6 deletions packages/malloy-render/src/component/render-result-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,24 @@ import {
valueIsNumber,
valueIsString,
} from './util';
import {hasAny} from './tag-utils';
import {
DataRowWithRecord,
RenderResultMetadata,
VegaChartProps,
VegaConfigHandler,
} from './types';
import {NULL_SYMBOL, shouldRenderAs} from './apply-renderer';
import {
NULL_SYMBOL,
shouldRenderAs,
shouldRenderChartAs,
} from './apply-renderer';
import {mergeVegaConfigs} from './vega/merge-vega-configs';
import {baseVegaConfig} from './vega/base-vega-config';
import {renderTimeString} from './render-time';
import {generateBarChartVegaSpec} from './bar-chart/generate-bar_chart-vega-spec';
import {createResultStore} from './result-store/result-store';
import {generateLineChartVegaSpec} from './line-chart/generate-line_chart-vega-spec';
import {parse} from 'vega';
import {parse, Config} from 'vega';

function createDataCache() {
const dataCache = new WeakMap<DataColumn, QueryData>();
Expand Down Expand Up @@ -250,17 +253,32 @@ function populateExploreMeta(
) {
const fieldMeta = metadata.field(f);
let vegaChartProps: VegaChartProps | null = null;
if (hasAny(tag, 'bar', 'bar_chart')) {
const chartType = shouldRenderChartAs(tag);
if (chartType === 'bar_chart') {
vegaChartProps = generateBarChartVegaSpec(f, metadata);
} else if (tag.has('line_chart')) {
} else if (chartType === 'line_chart') {
vegaChartProps = generateLineChartVegaSpec(f, metadata);
}

if (vegaChartProps) {
const vegaConfig = mergeVegaConfigs(
const vegaConfigOverride =
options.getVegaConfigOverride?.(vegaChartProps.chartType) ?? {};

const vegaConfig: Config = mergeVegaConfigs(
baseVegaConfig(),
options.getVegaConfigOverride?.(vegaChartProps.chartType) ?? {}
);

const maybeAxisYLabelFont = vegaConfigOverride['axisY']?.['labelFont'];
const maybeAxisLabelFont = vegaConfigOverride['axis']?.['labelFont'];
if (maybeAxisYLabelFont || maybeAxisLabelFont) {
const refLineFontSignal = vegaConfig.signals?.find(
signal => signal.name === 'referenceLineFont'
);
if (refLineFontSignal)
refLineFontSignal.value = maybeAxisYLabelFont ?? maybeAxisLabelFont;
}

fieldMeta.vegaChartProps = {
...vegaChartProps,
spec: {
Expand Down
16 changes: 16 additions & 0 deletions packages/malloy-render/src/component/render.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,19 @@
'calt' 1;
}
}

.malloy-copied-modal {
position: fixed;
background: #333;
font-size: 13px;
padding: 6px 12px;
border-radius: 4px;
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
color: white;
bottom: 24px;
left: 100%;
text-wrap: nowrap;

transition: all 0s;
animation: modal-slide-in 2s forwards;
}
15 changes: 12 additions & 3 deletions packages/malloy-render/src/component/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {ComponentOptions, ICustomElement} from 'component-register';
import {applyRenderer} from './apply-renderer';
import {
DashboardConfig,
DrillData,
MalloyClickEventPayload,
TableConfig,
VegaConfigHandler,
Expand All @@ -40,6 +41,7 @@ export type MalloyRenderProps = {
scrollEl?: HTMLElement;
modalElement?: HTMLElement;
onClick?: (payload: MalloyClickEventPayload) => void;
onDrill?: (drillData: DrillData) => void;
vegaConfigOverride?: VegaConfigHandler;
tableConfig?: Partial<TableConfig>;
dashboardConfig?: Partial<DashboardConfig>;
Expand All @@ -53,6 +55,7 @@ const ConfigContext = createContext<{
addCSSToShadowRoot: (css: string) => void;
addCSSToDocument: (id: string, css: string) => void;
onClick?: (payload: MalloyClickEventPayload) => void;
onDrill?: (drillData: DrillData) => void;
vegaConfigOverride?: VegaConfigHandler;
modalElement?: HTMLElement;
}>();
Expand Down Expand Up @@ -140,6 +143,7 @@ export function MalloyRender(
<ConfigContext.Provider
value={{
onClick: props.onClick,
onDrill: props.onDrill,
vegaConfigOverride: props.vegaConfigOverride,
element,
stylesheet,
Expand Down Expand Up @@ -213,9 +217,14 @@ export function MalloyRenderInner(props: {
};

return (
<ResultContext.Provider value={metadata()}>
{rendering().renderValue}
</ResultContext.Provider>
<>
<ResultContext.Provider value={metadata()}>
{rendering().renderValue}
</ResultContext.Provider>
<Show when={metadata().store.store.showCopiedModal}>
<div class="malloy-copied-modal">Copied query to clipboard!</div>
</Show>
</>
);
}

Expand Down
63 changes: 63 additions & 0 deletions packages/malloy-render/src/component/result-store/result-store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {createStore, produce, unwrap} from 'solid-js/store';
import {useResultContext} from '../result-context';
import {DrillData, RenderResultMetadata, DimensionContextEntry} from '../types';
import {Explore, Field} from '@malloydata/malloy';

interface BrushDataBase {
fieldRefId: string;
Expand Down Expand Up @@ -45,11 +47,13 @@ export type VegaBrushOutput = {

export interface ResultStoreData {
brushes: BrushData[];
showCopiedModal: boolean;
}

export function createResultStore() {
const [store, setStore] = createStore<ResultStoreData>({
brushes: [],
showCopiedModal: false,
});

const getFieldBrushBySourceId = (
Expand Down Expand Up @@ -108,6 +112,20 @@ export function createResultStore() {
return {
store,
applyBrushOps,
triggerCopiedModal: (time = 2000) => {
setStore(
produce(state => {
state.showCopiedModal = true;
})
);
setTimeout(() => {
setStore(
produce(state => {
state.showCopiedModal = false;
})
);
}, time);
},
};
}

Expand All @@ -117,3 +135,48 @@ export function useResultStore() {
const metadata = useResultContext();
return metadata.store;
}

export async function copyExplorePathQueryToClipboard({
metadata,
field,
dimensionContext,
onDrill,
}: {
metadata: RenderResultMetadata;
field: Field;
dimensionContext: DimensionContextEntry[];
onDrill?: (drillData: DrillData) => void;
}) {
const dimensionContextEntries = dimensionContext;
let explore: Field | Explore = field;
while (explore.parentExplore) {
explore = explore.parentExplore;
}

const whereClause = dimensionContextEntries
.map(entry => `\t\t${entry.fieldDef} = ${JSON.stringify(entry.value)}`)
.join(',\n');

const query = `
run: ${explore.name} -> {
where:
${whereClause}
} + { select: * }`.trim();

const drillData: DrillData = {
dimensionFilters: dimensionContextEntries,
copyQueryToClipboard: async () => {
try {
await navigator.clipboard.writeText(query);
metadata.store.triggerCopiedModal();
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to copy text: ', error);
}
},
query,
whereClause,
};
if (onDrill) onDrill(drillData);
else await drillData.copyQueryToClipboard();
}
53 changes: 1 addition & 52 deletions packages/malloy-render/src/component/table/table-context.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {createContext, useContext} from 'solid-js';
import {createStore, SetStoreFunction, Store} from 'solid-js/store';
import {TableLayout} from './table-layout';
import {Explore, Field} from '@malloydata/malloy';
import {DimensionContextEntry} from '../types';

type TableStore = {
headerSizes: Record<string, number>;
Expand All @@ -11,11 +11,6 @@ type TableStore = {
showCopiedModal: boolean;
};

export type DimensionContextEntry = {
fieldDef: string;
value: string | number | boolean | Date;
};

export type TableContext = {
root: boolean;
layout: TableLayout;
Expand All @@ -25,11 +20,6 @@ export type TableContext = {
currentRow: number[];
currentExplore: string[];
dimensionContext: DimensionContextEntry[];
copyExplorePathQueryToClipboard: (
tableCtx: TableContext,
field: Field,
dimensionContext: DimensionContextEntry[]
) => void;
};

export const TableContext = createContext<TableContext>();
Expand All @@ -43,44 +33,3 @@ export function createTableStore() {
showCopiedModal: false,
});
}

export async function copyExplorePathQueryToClipboard(
tableCtx: TableContext,
field: Field,
dimensionContext: DimensionContextEntry[]
) {
const dimensionContextEntries = [
...tableCtx!.dimensionContext,
...dimensionContext,
];
let explore: Field | Explore = field;
while (explore.parentExplore) {
explore = explore.parentExplore;
}

const whereClause = dimensionContextEntries
.map(entry => `\t\t${entry.fieldDef} is ${JSON.stringify(entry.value)}`)
.join(',\n');

const query = `
run: ${explore.name} -> {
where:
${whereClause}
} + { select: * }`.trim();

try {
await navigator.clipboard.writeText(query);
tableCtx.setStore(s => ({
...s,
showCopiedModal: true,
}));
setTimeout(() => {
tableCtx.setStore(s => ({
...s,
showCopiedModal: false,
}));
}, 2000);
} catch (error) {
console.error('Failed to copy text: ', error);
}
}
Loading

0 comments on commit 80faadd

Please sign in to comment.