react-vis icon indicating copy to clipboard operation
react-vis copied to clipboard

color quirks

Open jckr opened this issue 7 years ago • 7 comments

color sometimes works in strange and mysterious ways.

Categorical or linear scales don't work at the series level, except for lineSeries. ie

<XYPlot height={200} width={200} colorType='category' colorDomain={[0, 1, 2]} colorRange={['yellow', 'blue', 'red']}>
<VerticalBarSeries data={data} color={0} />
</XYPlot> 

or

<XYPlot height={200} width={200} colorType='category' colorDomain={[0, 1]} colorRange={[ 'blue', 'red']}>
<VerticalBarSeries data={data} color={0.5} />
</XYPlot> 

In both cases, the series will appear black. A scale will be created which will return an undefined value and override the defaults.

jckr avatar Jun 22 '17 18:06 jckr

still happens https://codepen.io/jckr/pen/yKOpma

jckr avatar Mar 14 '18 23:03 jckr

BarSeries and MarkSeries appear black in documentation.

zachguo avatar Apr 11 '18 23:04 zachguo

I think I'm experiencing the same issue here: https://codepen.io/anon/pen/PaxBaP?editors=0010

TheMcMurder avatar Jun 28 '18 15:06 TheMcMurder

I'm having the same issue with a VerticalBarSeries (NB: <FlexibleXYPlot> can be swapped out for XYPlot)

<FlexibleXYPlot height={350} xType="ordinal">
   <VerticalBarSeries animation cluster="things" data={data} />
</FlexibleXYPlot>
  • I'm setting a color property on each data list item to "green" (aka rgb(0, 128, 0))
  • <VerticalBarSeries> imports <BarSeries> which maps over every item in the data list
  • It applies these properties in a style object:
    style: {
      opacity: opacityFunctor && opacityFunctor(d),
      stroke: strokeFunctor && strokeFunctor(d),
      fill: fillFunctor && fillFunctor(d),
      ...style
    },
    
  • The fill and stroke properties come from
    this._getAttributeFunctor('fill') || this._getAttributeFunctor('color');
    
    and
    this._getAttributeFunctor('stroke') || this._getAttributeFunctor('color');
    
    respectively.
  • this._getAttributeFunctor is defined in <AbstractSeries> which <BarSeries> extends.
  • It returns getAttributeFunctor(this.props, attr), where getAttributeFunctor is defined in utils/scales-utils
  • The props are plentiful, and the important bits look like this:
    {
      ...
      colorDomain: (2) ["green", "green"],
      colorRange: (2) ["#EF5D28", "#FF9833"],
      data: [...],
      ...
    }  
    
  • The getAttributeFunctor function from utils/scales-utils looks like this:
    export function getAttributeFunctor(props, attr) {
      const scaleObject = getScaleObjectFromProps(props, attr);
      if (scaleObject) {
        const scaleFn = getScaleFnFromScaleObject(scaleObject);
        return d => scaleFn(_getAttrValue(d, scaleObject.accessor));
      }
      return null;
    }
    
  • getScaleObjectFromProps(props, attr) calls _collectScaleObjectFromProps with the same args.
  • This creates a scaleObject for use with the underlying d3 libs from the props:
    const {
      [attr]: value,
      [`_${attr}Value`]: fallbackValue,
      [`${attr}Range`]: range,  // !!
      [`${attr}Distance`]: distance = 0,
      [`${attr}BaseValue`]: baseValue,
      [`${attr}Type`]: type = LINEAR_SCALE_TYPE,
      [`${attr}NoFallBack`]: noFallBack,
      [`get${toTitleCase(attr)}`]: accessor = d => d[attr],
      [`get${toTitleCase(attr)}0`]: accessor0 = d => d[`${attr}0`]
    } = props;
    
    let {[`${attr}Domain`]: domain} = props; // !!
    
    In my case the important ones are colorRange and colorDomain
  • I end up with an scaleObject that looks like the following:
    {
      accessor: ƒ (d),
      accessor0: ƒ (d),
      attr: "color",
      baseValue: undefined,
      distance: 0,
      domain: (2) ["rgb(0, 128, 0)", "rgb(0, 128, 0)"],
      isValue: false,
      range: (2) ["rgb(239, 93, 40)", "rgb(255, 152, 51)"],
      type: "linear"
    }
    
  • Back in the getAttributeFunctor function, the scaleObject is returned
  • Now getAttributeFunctor calls getScaleFnFromScaleObject in
    if (scaleObject) {
      const scaleFn = getScaleFnFromScaleObject(scaleObject);
      return d => scaleFn(_getAttrValue(d, attr));
    }
    
    The returned function is going to be called with the data list (d) that is passed in as a prop to the VerticalBarSeries as <VerticalBarSeries ... data={data} /> (remember opacity: opacityFunctor && opacityFunctor(d) from <BarSeries>)
  • The _getAttrValue(d, scaleObject.accessor) returns the expected "green" value in RGB form: rgb(0, 128, 0) But when this is passed to the scaleFn (which comes from d3), it always returns rgb(0, 0, 0), and it is this value that gets applied to the stroke and fill properties in the data.map within BarSeries (explaining the black bars).
    {
      opacity: 1,
      stroke: "rgb(0, 0, 0)",
      fill: "rgb(0, 0, 0)"
    }
    

What I can't figure out is why the scaleFn always returns rgb(0, 0, 0) (black).

image

It might be a problem with this function in utils/scales-utils:

export function getScaleFnFromScaleObject(scaleObject) {
  ...

  const { type, domain, range } = scaleObject;

  /*
    My data is: 
    {
      attr: "color",
      baseValue: undefined,
      ...
      domain: (2) ["rgb(0, 128, 0)", "rgb(0, 128, 0)"],
      ...
      range: (2) ["rgb(239, 93, 40)", "rgb(255, 152, 51)"],
      type: "linear"
    }
  */

  const modDomain =
    ( domain[0] === domain[1] ) ? (
      ( domain[0] === 0 ) ? [-1, 0] : [-domain[0], domain[0]]
    ) : domain;

  /*
    My modDomain is: 
    [NaN, "rgb(0, 128, 0)"]

    My range (from above) is:
    ["rgb(239, 93, 40)", "rgb(255, 152, 51)"]
  */

  ...

  const scale = SCALE_FUNCTIONS[type]()
    .domain(modDomain)
    .range(range);

  ...

  return scale;
}

That NaN in [NaN, "rgb(0, 128, 0)"] looks pretty suspect, caused by trying to get a negative value of a string in [-domain[0], domain[0]] (aka [-"rgb(0, 128, 0)", "rgb(0, 128, 0)"])

JamesPeiris avatar Jul 17 '19 20:07 JamesPeiris

Is there any progress on this issue? I'm using a MarkSerie and everytime I try to set a color string (such as rgb or hex) it appears black no matter what.

Reg011 avatar Sep 03 '19 18:09 Reg011

Same here with LineMarkSeries!

marcadella avatar Apr 20 '21 09:04 marcadella

I'm not sure if this helps. but i used exact same code as @jckr except

  1. moved colorType='category' colorDomain={[0, 1, 2]} colorRange={['yellow', 'blue', 'red']} props from XYPlot to VerticalBarSeries props
  2. in data, add color:0~2
const data = [
        {x: 0, y: 8, color:0},
        {x: 1, y: 5, color:0},
        {x: 2, y: 4, color:1},
        {x: 3, y: 9, color:1},
        {x: 4, y: 1, color:2},
        {x: 5, y: 7, color:2},
        {x: 6, y: 6, color:1},
        {x: 7, y: 3, color:1},
        {x: 8, y: 2, color:0},
        {x: 9, y: 0, color:1}
    ];

<div>
    <XYPlot height={300} width={300}>
        <VerticalBarSeries colorType='category' colorDomain={[0, 1, 2]} colorRange={['yellow', 'blue', 'red']} data={data} />
    </XYPlot>
</div>

image

yblee85 avatar May 07 '21 02:05 yblee85