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

Instead of using d3 to imperatively compute DOM, use lit #1

Open
bennypowers opened this issue Jan 25, 2023 · 2 comments
Open

Instead of using d3 to imperatively compute DOM, use lit #1

bennypowers opened this issue Jan 25, 2023 · 2 comments

Comments

@bennypowers
Copy link

const g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

in this line, you're using d3's imperative api to define DOM, but Lit gives you a declarative api for this. consider this example, which uses d3 to compute geometry, but Lit to layout the DOM. Uou could extend this example to add lines and labels.

import { pie, arc } from 'd3';
import type { Arc, PieArcDatum, DefaultArcObject } from 'd3';

export type LabeledData = LabeledDatum[]

type ArcSlice = DefaultArcObject & PieArcDatum<LabeledDatum>;

@customElement('donut-chart')
export class DonutChart extends LitElement {
  @property({ attribute: false }) arcRatio = 0.8;

  @property({ attribute: false }) data: LabeledData[] = [];

  width = 100;

  get height(): number {
    return (this.width * (4 / 5));
  }

  get arcScale(): number {
    return 0.9;
  }

  constructor() {
    super();
    this.updateScalingFactor(this.getBoundingClientRect().width);
  }

  get radius(): number {
    return Math.min(this.width, this.height) / 2;
  }

  render(): TemplateResult {
    const { height, width } = this;
    const { arcRatio, arcScale, radius, scalingFactor } = this;

    const slices = pie<LabeledDatum>()
      .value(d => d.value)
      .sort(null)
      .sortValues(null)(this.data);

    const donutArc = arc()
      .innerRadius(radius * arcScale)
      .outerRadius(radius * arcScale * arcRatio);

    const xOffset = width / 2;
    const yOffset = height / 2;

    return svg`
      <svg id="pie" part="pie" viewBox="-${xOffset} -${yOffset} ${width} ${height}">
        ${slices.map((d: ArcSlice, i: number) => svg`
          <path data-slice="${d.data.key}"
                data-index="${i}"
                part="slice ${d.data.key}"
                vector-effect="non-scaling-stroke"
                d="${donutArc(d)}"
                title="${d.data.label ?? d.data.key} - ${d.data.value}"
          ></path>
        `)}
      </svg>
    `;
  }

  updated(changed: PropertyValues<this>): void {
    if (changed.has('width'))
      this.updateScalingFactor(this.getBoundingClientRect().width);
  }

  updateScalingFactor(width: number): void {
    this.scalingFactor = 2 * this.width / (width === 0 ? this.width : width);
  }
}
@JaySunSyn
Copy link
Owner

JaySunSyn commented Jan 28, 2023

Hehe, I'm surprised that someone actually looked at this repo (created live at a meeting to showcase 😅).

Def nicer to do it declaratively via lit, however may be harder to get into this for someone with d3 but without prior modern js / web component experience.

Thanks for the ts snippet, we may create a chart design system and rendering via lit instead of using d3 is a nice approach.

Tiny remark, I'd only use the svg function for svg fragments (as you do in the map) but not for the container.

@JaySunSyn
Copy link
Owner

JaySunSyn commented Jan 28, 2023

Btw, @bennypowers do you guys work on chart components at red hat?

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

No branches or pull requests

2 participants