Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roadmap for D3 Service #6

Closed
tomwanzek opened this issue Sep 13, 2016 · 11 comments
Closed

Roadmap for D3 Service #6

tomwanzek opened this issue Sep 13, 2016 · 11 comments
Labels

Comments

@tomwanzek
Copy link
Owner

This issue was opened as a forum to post/discuss ideas for enhancing the D3 Service for Angular 2.

Consideration to enhancements will be given to enhancements/changes which will retain a to be finalized reasonable project scope.

Example considerations are:

  • It may be very sensible to package the service into an Angular 2 modules as opposed to simply providing the service.
  • It could be worthwhile to add some custom type guards.

The scope and the resulting roadmap should, however, likely not extend to include e.g. D3 Components.

@amcdnl
Copy link

amcdnl commented Sep 13, 2016

You should check out my project: https://github.com/swimlane/ng2d3 - Angular2 draws the SVG's with this which starts letting u think about things like Server-side Rendering and Web Workers.

@tomwanzek
Copy link
Owner Author

@amcdnl Thanks for the project link, Austin, I will look at it, when I can devote the time it deserves 😄 !

As an aside, you can bump up the @types/d3-**** versions in your project dependencies. The major and minor version numbers are now correctly parsed for the published packages. I.e. getting the latest will align with the major and minor versions of actual d3 modules. Also, the latest had a few fixes/enhancements.

@bUKaneer
Copy link

Does the service support extensions, I'd like to use it to generate a word cloud - but am unsure as to how to pull in a new layout as an extension. The example here http://bl.ocks.org/ericcoopey/6382449 uses a cloud layout plugin - is this something that is supported?

I've already used the service for creating pie and donut charts and really appreciate the time and effort you've put into this - its brilliant!

@tomwanzek
Copy link
Owner Author

@bUKaneer Good question. A few things. There is currently no out-of-the-box support for "plug-ins" to be run through the service itself. Also, I think the specific layout for word clouds you referenced above, is still written with D3 v3 in mind. There is an open issue and PR to convert it to a D3 v4 "plug-in" module.

A short while back I played around with a couple of ideas to make the service a little more modular with respect to the modules in its current scope. Back of the napkin kind of pattern doodles. While I have not gotten around to prototyping it due to other priorities, I think, a comparable pattern may also serve to more generally add extensions into a project and use them in conjunction with the service.

With that being said, I will take the interim step to test something out with one of the other extensions which are already available for D3 v4. If the pattern I have in mind checks out, I might be in the position to provide the general guidance required for you to adapt it to a suitable word cloud extension.

@tomwanzek
Copy link
Owner Author

tomwanzek commented Nov 24, 2016

Alright, I did some brief testing with the d3-hsv and d3-scale-chromatic modules as "D3 v4 plug-ins".

With the current version of the d3-ng2-service unchanged, one way to go is to create a wrapper service, which "enhances" it with the additional functionality. E.g. for the above two plugins:

import { Injectable } from '@angular/core';
import { D3, D3Service } from 'd3-ng2-service';
import * as d3Hsv from 'd3-hsv';
import * as d3ScaleChromatic from 'd3-scale-chromatic';

export type EnhancedD3 = D3 & typeof d3Hsv & typeof d3ScaleChromatic;

@Injectable()
export class EnhancedD3Service {

  private enhancedD3: EnhancedD3;

  constructor(
    private d3Service: D3Service
  ) {
    this.enhancedD3 = Object.assign(d3Service.getD3(), d3Hsv, d3ScaleChromatic);
  }

  getEnhancedD3() {
    return this.enhancedD3;
  }

}

Wherever, you would have ordinarily injected the D3Service into a component, you now simply inject the EnhancedD3Service. The private d3 property in the component would be of type EnhancedD3 and be assigned as:

  constructor(element: ElementRef, d3EnhancedService: EnhancedD3Service) {
    this.d3 = d3EnhancedService.getEnhancedD3();
    this.parentNativeElement = element.nativeElement;
  }

This approach is fairly general, assuming that the "plugins" are well written to conform to the modular approach underlying D3 v4.

Current limitations are obviously:

  • the plugin itself must be for D3 v4
  • the plugin has current TypeScript definitions (natively or e.g. through DefinitelyTyped/@types)

Hope this helps in the interim.

When I have some time, I will consider, to which extent the above approach could be generalized and baked into d3-ng2-service directly.

In the interim, if this meets your needs, I should probably also add a little cookbook recipe to the README.

EDIT: Of course nothing prevents you from simply creating your own service from scratch, including any plug-ins of your choice. I.e. directly use the barrel/service pattern that is used in the source code for d3-ng2-service. That is the easiest way to tailor totally to your needs.

@bUKaneer
Copy link

Thank you so much for putting in the time on this, really appreciate it. I'm going to try your approach and see if I can get it working. Very much obliged, thanks for pointing me in the right direction!

@bUKaneer
Copy link

bUKaneer commented Nov 25, 2016

Hello again. I've spent some time today trying out this approach, its almost working but Im having a problem with the compiler (angular) not being able to find the d3-v4-cloud package that I've installed from here.

compiler output:
\src\app\shared\services\d3-service-extended\d3-service-extended.service.ts:3:25 Cannot find module 'd3-v4-cloud'.

Additionally I found a definition file for the d3 v3 version of the plugin and compared it to the approach taken in the d3-hsv extention example you mentioned that I found here. I modified the d3.cloud.d.ts file accordingly and added it into a folder /src/typings in my angular project and then included the location in the tsconfig.json file.

I was wondering whether you would be able to send me you entire ExtendedD3 source code as Id like to dig a little deeper into the difference between how I've implemented it and compare it to yours to see if I can spot the problem. Additionally I tried implementing the d3 v4 cloud plugin as a class within my project (which I still have the code for) and although I got it compiling was then unable to make it accessible through the EnhancedD3Service.

My tsconfig.json has this in now:

    "typeRoots": [
      "../node_modules/@types",
      "./typings"
    ]

d3-v4-cloud.d.ts:

export function cloud(): Cloud<cloud.Word>;
export function cloud<T extends cloud.Word>(): Cloud<T>;

interface Word {
    text?: string;
    font?: string;
    style?: string;
    weight?: string | number;
    rotate?: number;
    size?: number;
    padding?: number;
    x?: number;
    y?: number;
}

interface Cloud<T extends cloud.Word> {
    start(): Cloud<T>;
    stop(): Cloud<T>;

    timeInterval(): number;
    timeInterval(interval: number): Cloud<T>;

    words(): T[];
    words(words: T[]): Cloud<T>;

    size(): [number, number];
    size(size: [number, number]): Cloud<T>;

    font(): (datum: T, index: number) => string;
    font(font: string): Cloud<T>;
    font(font: (datum: T, index: number) => string): Cloud<T>;

    fontStyle(): (datum: T, index: number) => string;
    fontStyle(style: string): Cloud<T>;
    fontStyle(style: (datum: T, index: number) => string): Cloud<T>;

    fontWeight(): (datum: T, index: number) => string | number;
    fontWeight(weight: string | number): Cloud<T>;
    fontWeight(weight: (datum: T, index: number) => string | number): Cloud<T>;

    rotate(): (datum: T, index: number) => number;
    rotate(rotate: number): Cloud<T>;
    rotate(rotate: (datum: T, index: number) => number): Cloud<T>;

    text(): (datum: T, index: number) => string;
    text(text: string): Cloud<T>;
    text(text: (datum: T, index: number) => string): Cloud<T>;

    spiral(): (size: number) => (t: number) => [number, number];
    spiral(name: string): Cloud<T>;
    spiral(spiral: (size: number) => (t: number) => [number, number]): Cloud<T>;

    fontSize(): (datum: T, index: number) => number;
    fontSize(size: number): Cloud<T>;
    fontSize(size: (datum: T, index: number) => number): Cloud<T>;

    padding(): (datum: T, index: number) => number;
    padding(padding: number): Cloud<T>;
    padding(padding: (datum: T, index: number) => number): Cloud<T>;

    on(type: "word", listener: (word: T) => void): Cloud<T>;
    on(type: "end", listener: (tags: T[], bounds: { x: number; y: number }[]) => void): Cloud<T>;
    on(type: string, listener: (...args: any[]) => void): Cloud<T>;

    on(type: "word"): (word: T) => void;
    on(type: "end"): (tags: T[], bounds: { x: number; y: number }[]) => void;
    on(type: string): (...args: any[]) => void;
}

export var cloud: Cloud;

s3-service-extended.ts:

import { Injectable } from '@angular/core';
import { D3, D3Service } from 'd3-ng2-service';
import * as d3Cloud from 'd3-v4-cloud';

export type EnhancedD3 = D3 & typeof d3Cloud;

@Injectable()
export class EnhancedD3Service {

  private enhancedD3: EnhancedD3;

  constructor(
    private d3Service: D3Service
  ) {
    this.enhancedD3 = Object.assign(d3Service.getD3(), d3Cloud);
  }

  getEnhancedD3() {
    return this.enhancedD3;
  }

}

packages.json:


  "dependencies": {
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/core": "2.0.0",
    "@angular/forms": "2.0.0",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/router": "3.0.0",
    "angular-tag-cloud-module": "0.0.2",
    "core-js": "^2.4.1",
    "d3-ng2-service": "^1.1.3",
    "d3-v4-cloud": "^1.0.0",

Terribly sorry for bothering you again, but if by chance you spot something silly I've done/forgot to do, I'd be very much obliged if you could point me in the right direction again.

@tomwanzek
Copy link
Owner Author

@bUKaneer I have not forgotten, just did not have a chance to comment more fully. Will get back to you shortly with a suggestions.

@bUKaneer
Copy link

bUKaneer commented Nov 30, 2016

Hey no problem, Ive actually gone about it a slightly different way now and am very close to getting it working, when I dump the Enhanced D3 to the console I get a cloud member but not a get cloud member as per all the other things. Ive forked the d3 v4 plugin and added a .d.ts file to it and then npm installed it from git hub: https://github.com/bUKaneer/d3-v4-cloud/blob/master/index.d.ts

Im happy to write all this up on a Wiki entry or something once I've got it working as it's actually been really interesting and I think it would definitely have value to others trying to implement a similar thing. I could even do a repo on my git hub with the base angular project, d3 service and my cloud example (which Ill probably do anyway)!

D3 Enhanced:

import { Injectable } from '@angular/core';
import { D3, D3Service } from 'd3-ng2-service';
import * as d3Cloud from 'd3-cloud';
//import { d3Cloud } from './d3-cloud-plugin/d3-cloud.plugin.component';

export type EnhancedD3 = D3 & typeof d3Cloud;

@Injectable()
export class EnhancedD3Service {

  private enhancedD3: EnhancedD3;

  constructor(private d3Service: D3Service) {
    this.enhancedD3 = Object.assign(d3Service.getD3(), d3Cloud);
  }

  getEnhancedD3() {
    return this.enhancedD3;
  }
}

package.json:

    "d3-cloud": "git://github.com/bUKaneer/d3-v4-cloud.git",
    "d3-ng2-service": "^1.1.3",

@tomwanzek
Copy link
Owner Author

@bUKaneer I opened up a separate issue to track building the functionality to add plug-ins directly into the D3 Service. This would obviate the need to use the extra wrapper in principle.

It is still contingent on ensuring that the plug-in ecosystem explode to a space where separate authors end up writing conflicting modules (or TS definitions). I.e. inadvertently using duplicate identifiers The issue becomes the merging into the same d3 variable.

That being said, as I can tell from the above you are now on a fork-of-a-fork, because the upgrade of the original module has not been merged yet.

So adding the definitions in as index.d.ts is certainly the most direct way to get them into the module. One thing you should do in this case is add the following to the package.json:

"typings": "index.d.ts",

Ordinarily, this is what the compiler will look for to see if there are bundled definitions, if things have not changed recently.

when I dump the Enhanced D3 to the console I get a cloudmember but not a get cloud member as per all the other things.

Not sure, I fully follow on this one. If the merge works as expected, than yes, you would have d3.cloud() available. I do not see any reason, you could expect a get cloud member, though.

If, ultimately, you cannot leave the definitions bundled with the module itself, let me know and I can point out how to utilize the DefinitelyTyped to get them published to @types. The catch is, they will have to be in a folder named in correspondence to the published module they are meant for. That's a bit of the catch with your situation and the unmerged fork you forked from.

So, hopefully, the original module gets upgraded to D3 v4 soon and there is no need to jump through hoops.

P.S. before I move on considering/implementing the enhancement, I have prioritized a couple of PRs to DT related to D3 about to be merged and some more comments on the d3-hexbin definitions already in progress on DT. Than I will circle back to it. Cheers, T.

@krlng
Copy link

krlng commented Mar 4, 2017

@bUKaneer Did you manage to get it run? I tried to follow your code, but having trouble with some dispatching stuff. If you have a running example, it would be great, if you can share it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants