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

Ability to customize tick format #12

Closed
curran opened this issue Jun 27, 2019 · 10 comments
Closed

Ability to customize tick format #12

curran opened this issue Jun 27, 2019 · 10 comments

Comments

@curran
Copy link
Contributor

curran commented Jun 27, 2019

It would be nice to be able to define a function that formats tick values.

Not sure this is the right place in the stack to file the issue (perhaps better as an issue in vega-lite or vega), but from the JS API is definitely where I'd love to be able to define and pass in a tick formatting function.

For example (from Ordinal Scatter Plot):

  const xAxisTickFormat = number =>
    d3.format('.3s')(number)
      .replace('G', 'B');

Related:

image

/cc @mbostock @domoritz

This particular issue feels like a symptom of something greater that's missing in the Vega ecosystem - there's a formidable "Wall of abstraction" that seems impenetrable. Setting the background color feels like a similar symptom. If only there were some way to add JavaScript hooks into Vega itself, maybe we could bash some holes in the wall. It would be a departure from the pure JSON spec idea, but possible worth pursuing.

What if the Vega spec object could have JS functions attached to it?

Another idea: Use the SVG renderer and do post-processing of the ticks DOM.

@curran
Copy link
Contributor Author

curran commented Jun 27, 2019

The original example:

vl.markBar()
  .data({values: d3.zip(names, totals)})
  .encode(
    vl.y().fieldN("0").sort(null).axis({title: null}),
    vl.x().fieldQ("1").axis({orient: "top", format: "s", title: "Total revenue (est.)"})
  )
  .width(width)
  .autosize({type: "fit-x", contains: "padding"})
  .render()

What I'd like to be able to do:

const xAxisTickFormat = number =>
  d3.format('.3s')(number)
    .replace('G', 'B');

vl.markBar()
  .data({values: d3.zip(names, totals)})
  .encode(
    vl.y().fieldN("0").sort(null).axis({title: null}),
    vl.x().fieldQ("1").axis({orient: "top", format: xAxisTickFormat, title: "Total revenue (est.)"})
  )
  .width(width)
  .autosize({type: "fit-x", contains: "padding"})
  .render()

@domoritz
Copy link
Member

Your example function is simple enough that we can support it in our expression language. An easy solution that gets us 90% to what you are asking for is to give you access to the expressions in Vega-Lite. Note that you can already use expressions in Vega.

Vega actually allows you to use arbitrary functions in expressions but it's disabled by default because many people are using Vega in environments where arbitrary code execution is a security risk (e.g. Wikipedia). Therefore I doubt we will support passing functions anytime soon.

@curran
Copy link
Contributor Author

curran commented Jun 27, 2019

Thank you @domoritz for your response.

To orient myself I read about the expression language in these pages:

You mention the ability to "use arbitrary functions in expressions". Does that mean that in the Vega spec there would be an expression string that contains the implementation of the function, which would then be parsed and evaluated by vega-expression? This I can understand would be a security risk, as it's essentially exposing eval(). This, however, is not what I had in mind.

What I had in mind was to break from the strictly JSON-serializable format of the Vega specification, and add the ability to pass in a function for the tick formatter as part of the Vega spec JavaScript Object (making it no longer JSON-serializable). If the containing environment only allows JSON to be used as input to the spec (e.g. Wikipedia), then having the ability to pass in functions on the Vega spec object would not be a security risk, as the containing environment would only accept strictly JSON from user generated content. A potential attacker would have no way of attaching a function. This approach is different from exposing eval(), and would only be available for developers who are already writing JS that runs on the page.

Therefore, I believe what I'm proposing would not introduce any security risks, and it would enable developers to reach through Vega's "wall of abstraction" to do custom things (meaning, things that the Vega team has not considered and added APIs for). I think it would be a huge improvement. To me the strictly JSON spec format is unnecessarily restrictive in the case we are producing it using JavaScript. For example, constructing a string that's a valid Vega expression, then having it parsed and executed by the Vega runtime, seems rather circuitous when we could instead write an actual JavaScript function.

@curran
Copy link
Contributor Author

curran commented Jun 27, 2019

Related vega/vega#608

@curran
Copy link
Contributor Author

curran commented Jun 27, 2019

Proposed solution direction: vega/vega#1853

@domoritz
Copy link
Member

Thanks for explaining your idea more. I agree with you.

However, I wonder what you think about exposing expressions for formatting in Vega-Lite do that our specs are still serializable? When would it not be enough?

@domoritz
Copy link
Member

Since this issue is a duplicate of vega/vega#608, let's keep the conversation going over there.

@curran
Copy link
Contributor Author

curran commented Jun 28, 2019

@domoritz Exposing expressions for formatting in Vega-Lite may be able to address the immediate need, namely this part .replace('G', 'B'). Is there already syntax for String replacement? It may be that this is possible, and I just don't know how to go about it.

That said, what happens when someone wants a custom format that is unforeseen by the expression formatting provided? From a more philosophical point of view, I do like the idea of being able to pass functions. From a practical point of view, perhaps we could get by without doing this.

I think it does make sense to close this issue in favor of vega/vega#608, but at the same time, this issue is in the context of the JS API, where passing a custom function would make more sense than in the general case of Vega.

At the end of the day, what I'd really like to see is a working implementation that formats this original example https://observablehq.com/@mbostock/working-with-wikipedia-data using "$90B" rather than "90G".

Thanks for your input on this!

@arvind
Copy link
Member

arvind commented Jun 28, 2019

Hi @curran, what you're asking for is mostly possible in the Vega stack. Here is the same chart with a Vega expression function doing the string replacement in this block of code (I first created the chart in Vega-Lite, and then dropped down to the lower-level Vega for this customization):

  "axes": [
    {
      "scale": "x",
      "orient": "top",
      ...
      "format": "s",
      "encode": {
        "labels": {
           "update": {
             "text": {"signal": "replace(datum.label, 'G', 'B')"}
           }
        }
      }
    },

Right now, I don't think we surface the ability to modify axis/legend encodings within Vega-Lite but we can certainly consider what the best way of doing that is.

@curran
Copy link
Contributor Author

curran commented Jun 29, 2019

@arvind This is amazing! Thank you for putting the time into investigating this. It's reassuring that it's possible to do this in Vega. Looking forward to ways this might be doable in Vega-Lite, and especially in the Vega-Lite JavaScript API.

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

3 participants