Skip to content

Commit

Permalink
Initial implementation based on opendatafit-vega-view addon
Browse files Browse the repository at this point in the history
  • Loading branch information
echus committed Jun 1, 2022
1 parent 086940a commit a82a527
Show file tree
Hide file tree
Showing 12 changed files with 52,457 additions and 28 deletions.
153 changes: 153 additions & 0 deletions addon/modifiers/vega.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import Modifier, { ArgsFor, PositionalArgs, NamedArgs } from 'ember-modifier';
// import { registerDestructor } from '@ember/destroyable';
import * as Vega from 'vega';
import * as VegaLite from 'vega-lite';
import * as VegaTooltip from 'vega-tooltip';
import { isEmpty } from 'lodash';

import { Resource, View } from 'opendatafit-types';


const DEFAULT_CONFIG: Vega.Config = {
background: '#fff',
arc: { fill: '#3e5c69' },
area: { fill: '#3e5c69' },
line: { stroke: '#3e5c69' },
path: { stroke: '#3e5c69' },
rect: { fill: '#3e5c69' },
shape: { stroke: '#3e5c69' },
symbol: { fill: '#3e5c69' },
axis: {
domainWidth: 0.5,
grid: true,
labelPadding: 2,
tickSize: 5,
tickWidth: 0.5,
titleFontWeight: 'normal',
},
axisBand: { grid: false },
axisX: { gridWidth: 0.2 },
axisY: { gridDash: [3], gridWidth: 0.4 },
legend: { labelFontSize: 11, padding: 1, symbolType: 'square' },
range: {
category: [
'#3e5c69',
'#6793a6',
'#182429',
'#0570b0',
'#3690c0',
'#74a9cf',
'#a6bddb',
'#e2ddf2',
],
},
};


interface VegaLiteArgs {
Args: {
Named: {
specType: 'vega-lite';
spec: VegaLite.TopLevelSpec;
data: Record<string, Record<string, number | null>>;
config: Vega.Config;
};
Positional: never;
}
}

interface VegaArgs {
Args: {
Named: {
specType: 'vega';
spec: Vega.Spec;
data: Record<string, Record<string, number | null>>;
config: Vega.Config;
};
Positional: never;
}
}

type VegaModifierSignature = VegaArgs | VegaLiteArgs;


export default class VegaModifier extends Modifier<VegaModifierSignature> {
private _vegaView!: Vega.View;

constructor(
owner: unknown,
args: ArgsFor<VegaModifierSignature>
) {
super(owner, args);
}

async modify(
element: Element,
positionalArgs: PositionalArgs<VegaModifierSignature>,
args: NamedArgs<VegaModifierSignature>
) {
if (!element) {
throw new Error('Vega has no element');
}

let tooltipHandler = new VegaTooltip.Handler();

let config = {};
if (isEmpty(args.config)) {
config = DEFAULT_CONFIG;
} else {
config = args.config;
}

let viewSpec = null;
if (args.specType === 'vega-lite') {
viewSpec = VegaLite.compile(args.spec).spec;
} else {
viewSpec = args.spec
}

this._vegaView = new Vega.View(Vega.parse(viewSpec, config), {
renderer: 'svg', // Renderer (canvas or svg)
container: this.element, // Parent DOM container
hover: true, // Enable hover processing
// autosize: { type: 'fit', resize: true },
tooltip: tooltipHandler.call,
});

await this._vegaView.runAsync();

// await this.populateData();
for (const name of Object.keys(args.data)) {
this._vegaView.data(name, args.data[name]);
}
await this._vegaView.runAsync();

this.element.classList.add('vega-view-modifier');

// TODO: Temporary bug fix for incorrect chart sizing on load before
// first resize
window.dispatchEvent(new Event('resize'));
}

private _setup(): void {
}

willDestroy() {
if (this._vegaView) {
this._vegaView.finalize();
}

this.element.classList.remove('vega-view-modifier');
}

// async didUpdateArguments() {
// if (!this._vegaView) {
// await this._setup();
// } else {
// // Already loaded, we don't expect the view/config to change
// // Just need to update data package
// await this.populateData();
// await this._vegaView.runAsync();
// }
// }
}
1 change: 1 addition & 0 deletions app/modifiers/vega.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-vega-modifier/modifiers/vega';
40 changes: 37 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,59 @@
"start": "ember serve",
"test": "npm-run-all lint test:*",
"test:ember": "ember test",
"test:ember-compatibility": "ember try:each"
"test:ember-compatibility": "ember try:each",
"prepack": "ember ts:precompile",
"postpack": "ember ts:clean"
},
"dependencies": {
"ember-auto-import": "^2.4.2",
"ember-lodash": "^4.19.5",
"@types/lodash": "^4.14.182",
"ember-modifier": "^3.2.7",
"ember-cli-babel": "^7.26.11",
"ember-cli-htmlbars": "^6.0.1"
"ember-cli-htmlbars": "^6.0.1",
"ember-cli-typescript": "^5.1.0",
"vega": "^5.22.1",
"vega-lite": "^5.2.0",
"vega-tooltip": "^0.28.0",
"opendatafit-types": "git+ssh://[email protected]:opendatafit/opendatafit-types.git"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.7.0",
"@embroider/test-setup": "^1.6.0",
"@glimmer/component": "^1.1.2",
"@glimmer/tracking": "^1.1.2",
"@types/ember-qunit": "^5.0.0",
"@types/ember-resolver": "^5.0.11",
"@types/ember__application": "^4.0.0",
"@types/ember__array": "^4.0.1",
"@types/ember__component": "^4.0.8",
"@types/ember__controller": "^4.0.0",
"@types/ember__debug": "^4.0.1",
"@types/ember__engine": "^4.0.0",
"@types/ember__error": "^4.0.0",
"@types/ember__object": "^4.0.2",
"@types/ember__polyfills": "^4.0.0",
"@types/ember__routing": "^4.0.7",
"@types/ember__runloop": "^4.0.1",
"@types/ember__service": "^4.0.0",
"@types/ember__string": "^3.0.9",
"@types/ember__template": "^4.0.0",
"@types/ember__test": "^4.0.0",
"@types/ember__test-helpers": "^2.6.1",
"@types/ember__utils": "^4.0.0",
"@types/htmlbars-inline-precompile": "^3.0.0",
"@types/qunit": "^2.11.3",
"@types/rsvp": "^4.0.4",
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"ember-auto-import": "^2.4.1",
"ember-cli": "~4.4.0",
"ember-cli-dependency-checker": "^3.3.1",
"ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-sri": "^2.1.1",
"ember-cli-terser": "^4.0.2",
"ember-cli-typescript-blueprints": "^3.0.0",
"ember-disable-prototype-extensions": "^1.1.3",
"ember-export-application-global": "^2.0.1",
"ember-load-initializers": "^2.1.2",
Expand All @@ -64,6 +97,7 @@
"prettier": "^2.6.2",
"qunit": "^2.19.1",
"qunit-dom": "^2.0.0",
"typescript": "^4.7.2",
"webpack": "^5.72.1"
},
"engines": {
Expand Down
14 changes: 14 additions & 0 deletions tests/dummy/app/config/environment.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default config;

/**
* Type declarations for
* import config from 'my-app/config/environment'
*/
declare const config: {
environment: string;
modulePrefix: string;
podModulePrefix: string;
locationType: 'history' | 'hash' | 'none' | 'auto';
rootURL: string;
APP: Record<string, unknown>;
};
110 changes: 110 additions & 0 deletions tests/dummy/app/controllers/application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { cloneDeep } from 'lodash';

import { Resource, View } from 'opendatafit-types';

import {
DATAPACKAGE,
DATA,
FIT_DATA,
DATA_2,
FIT_DATA_2
} from '../utils/data';


export default class Application extends Controller {
@tracked dataResource!: Resource;
@tracked fitResource!: Resource;

constructor() {
super(...arguments);

this.dataResource = DATAPACKAGE.resources[4] as Resource;
this.dataResource.data = DATA;

this.fitResource = DATAPACKAGE.resources[5] as Resource;
this.fitResource.data = FIT_DATA;
}

get resources() {
return [
this.dataResource,
this.fitResource
];
}

get data() {
type Data = Record<string, number | null>;
let data: Record<string, Data> = {};
data[this.dataResource.name] = this.dataResource.data as Data;
data[this.fitResource.name] = this.fitResource.data as Data;
return data;
}

get spec() {
return this.view.spec;
}

get specType() {
return this.view.specType;
}

get view() {
return DATAPACKAGE.views[4] as View;
}

get config() {
return {
"background": "#fff",
"arc": {"fill": "#3e5c69"},
"area": {"fill": "#3e5c69"},
"line": {"stroke": "#3e5c69"},
"path": {"stroke": "#3e5c69"},
"rect": {"fill": "#3e5c69"},
"shape": {"stroke": "#3e5c69"},
"symbol": {"fill": "#3e5c69"},
"axis": {
"domainWidth": 0.5,
"grid": true,
"labelPadding": 2,
"tickSize": 5,
"tickWidth": 0.5,
"titleFontWeight": "normal"
},
"axisBand": {"grid": false},
"axisX": {"gridWidth": 0.2},
"axisY": {"gridDash": [3], "gridWidth": 0.4},
"legend": {"labelFontSize": 11, "padding": 1, "symbolType": "square"},
"range": {
"category": [
"#3e5c69",
"#6793a6",
"#182429",
"#0570b0",
"#3690c0",
"#74a9cf",
"#a6bddb",
"#e2ddf2"
]
}
};
}

@action updateData() {
let updatedDataResource = cloneDeep(this.dataResource)
updatedDataResource.data = DATA_2;
this.dataResource = updatedDataResource;
let updatedFitResource = cloneDeep(this.fitResource)
updatedFitResource.data = FIT_DATA_2;
this.fitResource = updatedFitResource;
}
}

// DO NOT DELETE: this is how TypeScript knows how to look up your controllers.
declare module '@ember/controller' {
interface Registry {
'application': Application;
}
}
21 changes: 18 additions & 3 deletions tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
{{page-title "Dummy"}}
<h2 id="title">ember-vega-modifier test</h2>

<h2 id="title">Welcome to Ember</h2>
<div
{{vega
specType=this.specType
spec=this.spec
data=this.data
config=this.config
}}
style="height: 100%; min-height: 400px; max-height: 100%;"
>
</div>

{{outlet}}
<div>
<button {{on "click" this.updateData}}>
Update data
</button>
</div>

{{outlet}}
Loading

0 comments on commit a82a527

Please sign in to comment.