cogsie
Soy nuevo en la programación y ni siquiera estoy seguro de cómo hacer esta pregunta. Por favor, hágamelo saber si algo no está claro o si necesita más información, pero aquí va:
Estoy creando mi primer objeto visual personalizado en Power BI. Quiero tener gráficos de barras superpuestos. Encontré un ejemplo/tutorial en línea que muestra cómo crear un gráfico de barras simple. Pude modificar el visual para obtener los dos gráficos de barras superpuestos, y el visual personalizado se ve exactamente como lo quiero (ver a continuación).
El problema es que el ejemplo que modifiqué restringió el visual a usar solo una medida (es por eso que en la captura de pantalla las barras delgadas amarillas y gruesas verdes tienen exactamente la misma altura; ambas se basan en el mismo conjunto de valores). Pude cambiar el archivo de capacidades para permitir 2 medidas. Sin embargo, no puedo entender cómo hacerlo, por lo que el segundo conjunto de barras (barras amarillas) usa la segunda medida. ¿Puede alguien ayudarme a señalarme en la dirección correcta?
A continuación se muestra el archivo visual.ts. Por favor, hágamelo saber si necesita información adicional o si algo no está claro.
import "./../style/visual.less";
import powerbi from "powerbi-visuals-api";
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import IVisual = powerbi.extensibility.visual.IVisual;
import DataView = powerbi.DataView;
import DataViewValueColumn = powerbi.DataViewValueColumn;
import DataViewCategorical = powerbi.DataViewCategorical;
import DataViewCategoricalColumn = powerbi.DataViewCategoricalColumn;
import DataViewCategoryColumn = powerbi.DataViewCategoryColumn;
import PrimitiveValue = powerbi.PrimitiveValue;
import IVisualHost = powerbi.extensibility.visual.IVisualHost;
import IColorPalette = powerbi.extensibility.IColorPalette;
import VisualObjectInstance = powerbi.VisualObjectInstance;
import VisualObjectInstanceEnumeration = powerbi.VisualObjectInstanceEnumeration;
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;
import Fill = powerbi.Fill;
import VisualTooltipDataItem = powerbi.extensibility.VisualTooltipDataItem;
import ISelectionManager = powerbi.extensibility.ISelectionManager;
import { valueFormatter as vf, textMeasurementService as tms } from "powerbi-visuals-utils-formattingutils";
import IValueFormatter = vf.IValueFormatter;
import { VisualSettings,BarchartProperties } from "./settings";
import * as d3 from "d3";
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;
type DataSelection<T> = d3.Selection<d3.BaseType, T, any, any>;
export interface BarchartDataPoint{
Category: string;
Value: number;
}
export interface BarchartViewModel{
IsNotValid: boolean;
DataPoints?:BarchartDataPoint[];
Format?: string,
SortBySize?: boolean;
XAxisFontSize?: number;
YAxisFontSize?: number;
BarColor?: string
ColumnName?: string;
MeasureName?: string;
}
export class Barchart implements IVisual {
private svg: Selection<SVGElement>;
private barContainer: Selection<SVGGElement>;
private plotBackground: Selection<SVGRectElement>;
private barSelection: DataSelection<BarchartDataPoint>;
private xAxisContainer: Selection<SVGGElement>;
private yAxisContainer: Selection<SVGGElement>;
private barContainer2: Selection<SVGGElement>;
private plotBackground2: Selection<SVGRectElement>;
private barSelection2: DataSelection<BarchartDataPoint>;
private xAxisContainer2: Selection<SVGGElement>;
private yAxisContainer2: Selection<SVGGElement>;
private hostService: IVisualHost;
private settings: VisualSettings;
private viewModel: BarchartViewModel;
private static margin = {
top:20,
right: 20,
bottom: 20,
left: 50,
};
constructor(options: VisualConstructorOptions) {
console.log('Constructor executing', options);
this.hostService = options.host;
this.svg = d3.select(options.element)
.append('svg')
.classed('Barchart',true);
this.barContainer = this.svg
.append('g')
.classed('barContainer', true);
this.plotBackground = this.barContainer
.append('rect')
.classed('plotBackground', true);
this.xAxisContainer = this.svg
.append('g')
.classed('xAxis', true);
this.yAxisContainer = this.svg
.append('g')
.classed('yAxis', true);
this.barContainer2 = this.svg
.append('g')
.classed('barContainer2', true);
this.plotBackground2 = this.barContainer2
.append('rect')
.classed('plotBackground2', true);
this.xAxisContainer2 = this.svg
.append('g')
.classed('xAxis2', true);
this.yAxisContainer2 = this.svg
.append('g')
.classed('yAxis2', true);
this.settings = VisualSettings.getDefault() as VisualSettings;
}
public update(options: VisualUpdateOptions) {
var viewModel: BarchartViewModel = this.createViewModel(options.dataViews[0]);
if (viewModel.IsNotValid){
return;
}
//set height and width of root SVG element using viewport passed by Power BI host
this.svg.attr("height",options.viewport.height);
this.svg.attr("width", options.viewport.width);
let marginLeft = Barchart.margin.left * (viewModel.YAxisFontSize / 10);
let marginBottom = Barchart.margin.bottom * (viewModel.XAxisFontSize / 10);
let marginTop = Barchart.margin.top;
let marginRight = Barchart.margin.right;
let plotArea = {
x: marginLeft,
y:marginTop,
width: (options.viewport.width - (marginLeft + Barchart.margin.right))/2,
height: (options.viewport.height - (marginTop + marginBottom)),
};
let plotArea2 = {
x: plotArea.x + plotArea.width + 0.3,
y: marginTop,
};
this.barContainer
.attr("transform","translate(" + plotArea.x + "," + plotArea.y + ")")
.attr("width",options.viewport.width)
.attr("height", options.viewport.height);
this.plotBackground
.attr("width", plotArea.width)
.attr("height", plotArea.height)
.style("fill","blue");
this.barContainer2
.attr("transform", "translate(" + plotArea.x + "," + plotArea.y + ")")
.attr("width", options.viewport.width)
.attr("height", options.viewport.height);
this.plotBackground2
.attr("width", plotArea.width)
.attr("height", plotArea.height)
.style("fill", "none");
var xScale = d3.scaleBand()
.rangeRound([0, plotArea.width])
.padding(0.1)
.domain(viewModel.DataPoints.map((dataPoint:BarchartDataPoint) => dataPoint.Category));
this.xAxisContainer
.attr("class", "xAxis")
.attr("transform","translate(" + plotArea.x + "," + (plotArea.height + plotArea.y)+")")
.call(d3.axisBottom(xScale));
this.xAxisContainer2
.attr("class", "xAxis2")
.attr("transform", "translate(" + plotArea.x + "," + (plotArea.height + plotArea.y) + ")")
.call(d3.axisBottom(xScale));
d3.select(".xAxis").selectAll("text").style("font-size",viewModel.XAxisFontSize);
d3.select(".xAxis2").selectAll("text").style("font-size", viewModel.XAxisFontSize);
let maxValueY: number = d3.max(viewModel.DataPoints,(dataPoint:BarchartDataPoint) => +(dataPoint.Value));
var valueFormatter = vf.create({
format: viewModel.Format,
value: maxValueY/100,
cultureSelector: this.hostService.locale
});
var yScale = d3.scaleLinear()
.rangeRound([plotArea.height,0])
.domain([0,maxValueY * 1.02]);
var yAxis = d3.axisLeft(yScale)
.tickFormat((d) => valueFormatter.format(d));
// .tickPadding(12).ticks(5);
this.yAxisContainer
.attr("class","yAxis")
.attr("transform", "translate(" + plotArea.x + "," + plotArea.y + ")")
.call(yAxis);
/*
this.yAxisContainer2
.attr("class", "yAxis2")
.attr("transform", "translate(" + plotArea2.x + "," + plotArea.y + ")")
.call(yAxis);
*/
d3.select(".yAxis").selectAll("text").style("font-size",viewModel.YAxisFontSize);
// d3.select(".yAxis2").selectAll("text").style("font-size", viewModel.YAxisFontSize);
this.barSelection2 = this.barContainer2
.selectAll('.bar')
.data(viewModel.DataPoints);
this.barSelection = this.barContainer
.selectAll('.bar')
.data(viewModel.DataPoints);
const barSelectionMerged = this.barSelection
.enter()
.append('rect')
.merge(<any>this.barSelection)
.classed('bar',true);
const barSelectionMerged2 = this.barSelection2
.enter()
.append('rect')
.merge(<any>this.barSelection2)
.classed('bar', true);
barSelectionMerged
.attr("x", (dataPoint: BarchartDataPoint) => xScale(dataPoint.Category))
.attr("y", (dataPoint: BarchartDataPoint) => yScale(Number(dataPoint.Value)))
.attr("width", xScale.bandwidth())
.attr("height", (dataPoint: BarchartDataPoint) => (plotArea.height - yScale(Number(dataPoint.Value))))
.style("fill",(dataPoint:BarchartDataPoint) => viewModel.BarColor);
barSelectionMerged2
.attr("x", (dataPoint: BarchartDataPoint) => xScale(dataPoint.Category) + xScale.bandwidth() / 4)
.attr("y", (dataPoint: BarchartDataPoint) => yScale(Number(dataPoint.Value)))
.attr("width", xScale.bandwidth() / 2)
.attr("height", (dataPoint: BarchartDataPoint) => (plotArea.height - yScale(Number(dataPoint.Value))))
.style("fill", (dataPoint: BarchartDataPoint) => 'yellow')
.style("fill-opacity", (dataPoint: BarchartDataPoint) => 1);
this.barSelection
.exit()
.remove();
}
public createViewModel(dataView: DataView): BarchartViewModel{
//handle case where categorical DataView is not valid
if(typeof dataView === "undefined" ||
typeof dataView.categorical === "undefined" ||
typeof dataView.categorical.categories === "undefined" ||
typeof dataView.categorical.values === "undefined"){
return {IsNotValid: true};
}
this.settings=VisualSettings.parse(dataView) as VisualSettings;
var categoricalDataView: DataViewCategorical = dataView.categorical;
var categoryColumn: DataViewCategoricalColumn = categoricalDataView.categories[0];
var categoryNames: PrimitiveValue[] = categoricalDataView.categories[0].values;
var categoryValues: PrimitiveValue[] = categoricalDataView.values[0].values;
var BarchartDataPoints: BarchartDataPoint[] = [];
for(var i=0; i < categoryValues.length; i++){
//get category name and category value
var category : string = <string>categoryNames[i];
var categoryValue: number = <number>categoryValues[i];
//add new data point to barchartDataPoints collection
BarchartDataPoints.push({
Category: category,
Value: categoryValue
});
}
//get formatting code for the field that is the measure
var format: string = categoricalDataView.values[0].source.format
//get persistent property values
var SortBySize: boolean = this.settings.barchartProperties.sortBySize;
var xAxisFontSize: number = this.settings.barchartProperties.xAxisFontSize;
var yAxisFontSize: number = this.settings.barchartProperties.yAxisFontSize;
var barColor: string = typeof (this.settings.barchartProperties.barColor) == "string"?
this.settings.barchartProperties.barColor:
this.settings.barchartProperties.barColor.solid.color;
//sort dataset rows by measure value instead of cateogry value
if(SortBySize){
BarchartDataPoints.sort((x,y) =>{return y.Value - x.Value})
}
//return view model to upate method
return{
IsNotValid: false,
DataPoints: BarchartDataPoints,
Format: format,
SortBySize: SortBySize,
BarColor: barColor,
XAxisFontSize: xAxisFontSize,
YAxisFontSize: yAxisFontSize,
ColumnName: dataView.metadata.columns[1].displayName,
MeasureName:dataView.metadata.columns[0].displayName
};
}
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
var visualObjects: VisualObjectInstanceEnumerationObject = <VisualObjectInstanceEnumerationObject>VisualSettings.enumerateObjectInstances(this.settings, options);
visualObjects.instances[0].validValues = {
xAxisFontSize:{numberRange:{min: 10, max:36}},
yAxisFontSize: { numberRange: { min: 10, max: 36 } },
};
return visualObjects
}
}
dm-p
En respuesta a cogsie
¡Estupendo!
He agregado todos los archivos a esta esencia para que descargues el código completo, ya que me gustaría centrarme en explicar los cambios que hice para que puedas desarrollarlos en consecuencia.
capacidades.json – roles de datos
he modificado el roles de datos a lo siguiente:
"dataRoles": [
{
"displayName": "Bar Grouping",
"name": "myCategory",
"kind": "Grouping"
},
{
"displayName": "Actual",
"name": "actual",
"kind": "Measure"
},
{
"displayName": "Budget",
"name": "budget",
"kind": "Measure"
}
]
Esto modificará los roles de datos del objeto visual de la siguiente manera:
Nota que su visual de desarrollo probablemente todavía tendrá el ahora eliminado miMedida allí, por lo que su mejor apuesta es asegurarse de que todo se elimine por completo y se vuelva a aplicar cuando trabaje con el nuevo código que he proporcionado.
capacidades.json – dataViewMappings
El dataViewMappings ahora mira de la siguiente manera:
"dataViewMappings": [
{
"conditions": [
{
"myCategory": {
"max": 1
},
"actual": {
"max": 1
},
"budget": {
"max": 1
}
}
],
"categorical": {
"categories": {
"for": {
"in": "myCategory"
},
"dataReductionAlgorithm": {
"top": {}
}
},
"values": {
"select": [
{
"bind": {
"to": "actual"
}
},
{
"bind": {
"to": "budget"
}
}
]
}
}
}
]
Esto se agrupará por miCategoría y luego enlazar ambas medidas a cada uno. Para mis datos de prueba, la tabla visual tiene el siguiente aspecto:
Tenga en cuenta que este es un conjunto de datos público y estoy tratando Propina como real y Total Facturar como presupuesto.
Echemos un vistazo a la dataViewMapping en la visualización del desarrollador para los datos que he agregado:
La matriz de valores ahora tiene dos entradas: una para real y uno para presupuesto. Podemos ver esto si ampliamos el fuente objeto para cada uno y luego roles – y así es como puede determinar qué entrada está desempeñando qué rol. Los valores de medida para cada uno están entonces en el asociado valores matriz (que coincidirá con el orden de miCategoría). Por brevedad, solo he ampliado esto para real.
visual.ts – BarchartDataPoint interfaz
Necesitamos cambiar la especificación de la interfaz para asegurarnos de que la ‘forma’ de cada punto de datos sea correcta. Esto ahora se ve de la siguiente manera:
export interface BarchartDataPoint{
category: string;
actual: number;
budget: number;
}
(no es esencial, pero es una buena práctica usar camelCase para las propiedades, por lo que he modificado todo para adaptarlo)
Esto ahora significa que nuestro modelo de vista espera dos valores numéricos para cada categoría, lo que coincide con nuestro dataViewMappings, por lo que debemos mapearlos en el modelo de vista a continuación.
visual.ts – crearViewModel función
Si está realizando los cambios anteriores de forma incremental, ahora verá errores en su código, porque el BarchartDataPoint la ‘forma’ de la interfaz no coincide con el objeto que está asignando en el crearViewModel función. Necesitamos actualizar esto.
Con lo anterior en mente, y cómo el dataViewMapping mira, necesitamos cambiar la forma en que iteramos sobre él.
- Anteriormente estaba iterando sobre los valores de medida; He modificado esto para iterar sobre los valores de categoría solo para hacer que el siguiente paso sea más fácil.
- Voy a hacer la solución más simple posible para las medidas usando descriptores de acceso de matriz de [0] por real y [1] por presupuesto.
- Para hacer que su código sea súper resistente, debería filtrar la matriz de valores por papel para garantizar que el valor se proporcione definitivamente, pero podemos preocuparnos por eso mucho más tarde 😉
La parte del código de función para mapear el modelo de vista ahora tiene el siguiente aspecto:
categoryNames.map((c, ci) => { /** c= category, ci = category array index */
BarchartDataPoints.push({
category: <string>c,
actual: <number>categoricalDataView.values[0].values[ci],
budget: <number>categoricalDataView.values[1].values[ci]
});
});
Tiene una clasificación más abajo, por lo que se modificó para usar el real valor:
if(SortBySize){
BarchartDataPoints.sort((x,y) =>{return y.actual - x.actual})
}
visual.ts – actualizar función
Ahora es un caso o vincular todo, desde el modelo de vista correctamente a la lógica de su gráfico.
La primera solución es tu maxValueY asignación: solo conseguimos que coincida con el valor más alto ahora que tiene dos medidas:
let maxValueY: number = d3.max(
viewModel.DataPoints,
(dataPoint:BarchartDataPoint) =>
/** Get the higher of either measure per group */
+Math.max(dataPoint.actual, dataPoint.budget)
);
¡Ahora, la forma real! barraSelecciónFusionada se actualiza para usar ahora el real propiedad del punto de datos:
barSelectionMerged
.attr("x", (dataPoint: BarchartDataPoint) => xScale(dataPoint.category))
.attr("y", (dataPoint: BarchartDataPoint) => yScale(Number(dataPoint.actual)))
.attr("width", xScale.bandwidth())
.attr("height", (dataPoint: BarchartDataPoint) => (plotArea.height - yScale(Number(dataPoint.actual))))
.style("fill",(dataPoint:BarchartDataPoint) => viewModel.BarColor);
… y barSelectionMerged2 obtiene el presupuesto propiedad como parte de su yEscala:
barSelectionMerged2
.attr("x", (dataPoint: BarchartDataPoint) => xScale(dataPoint.category) + xScale.bandwidth() / 4)
.attr("y", (dataPoint: BarchartDataPoint) => yScale(Number(dataPoint.budget)))
.attr("width", xScale.bandwidth() / 2)
.attr("height", (dataPoint: BarchartDataPoint) => (plotArea.height - yScale(Number(dataPoint.budget))))
.style("fill", (dataPoint: BarchartDataPoint) => 'yellow')
.style("fill-opacity", (dataPoint: BarchartDataPoint) => 1);
Verificando
¡Ahora podemos probar en el desarrollador visual! Así es como se ven mis datos arriba:
Y con suerte, aquí es donde debes estar. 🙂
¡Buena suerte!
Daniel
Si mi publicación resuelve su desafío, considere aceptarla como una solución para ayudar a otros miembros del foro a encontrar la respuesta más rápidamente. 🙂
dm-p
Hola @cogsie,
Puedo ayudarte con esto. ¿Puedes adjuntar tu capacidades.json ¿también? Esto especificará cómo se agrega la segunda medida a la vista de datos y puedo asegurarme de proporcionarle el código correcto.
Gracias,
Daniel
cogsie
En respuesta a dm-p
Muchas gracias. Estaba investigando un poco más sobre esto y creo que tiene algo que ver con usar for…in en lugar de bind…to.
A continuación se muestran las capacidades JSON
{
"dataRoles": [
{
"displayName": "Bar Grouping",
"name": "myCategory",
"kind": "Grouping"
},
{
"displayName": "Bar Measurement",
"name": "myMeasure",
"kind": "Measure"
}
],
"dataViewMappings": [
{
"conditions": [
{
"myCategory": {
"max": 1
},
"myMeasure": {
"max": 2
}
}
],
"categorical": {
"categories": {
"for": {
"in": "myCategory"
},
"dataReductionAlgorithm": {
"top": {}
}
},
"values": {
"select": [
{
"bind": {
"to": "myMeasure"
}
}
]
}
}
}
],
"objects": {
"barchartProperties": {
"displayName": "Barchart Properties",
"properties": {
"sortBySize": {
"displayName": "Sort by Size",
"type": {
"bool": true
}
},
"barColor": {
"displayName": "Bar Color",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"xAxisFontSize": {
"displayName": "X Axis Font Size",
"type": {
"integer": true
}
},
"yAxisFontSize": {
"displayName": "Y Axis Font Size",
"type": {
"integer": true
}
}
}
}
}
}
dm-p
En respuesta a cogsie
Hola, @cogsie: acabo de configurar tu código y recibo algunos errores de compilación debido a mi configuración.ts no estar sincronizado con el código en su visual.ts. ¿Puede proporcionar eso también (lo siento, debería haber pensado en pedirlo originalmente)?
Otra pregunta: ¿su visual tiene la intención de mostrar solo un máximo de dos medidas, y la segunda medida está desempeñando un papel específico (por ejemplo, como un objetivo frente a la otra medida)? Esto es importante porque afectará la forma en que construimos su modelo de vista y capacidades.json para adaptarse
Gracias,
Daniel
cogsie
En respuesta a dm-p
Realmente aprecio tu ayuda.
A continuación se muestra el archivo settings.ts.
Mi objetivo con este gráfico es comparar valores en una o más categorías. Por ejemplo, comparar montos presupuestados con montos reales para cada región de ventas donde las barras verdes son el presupuesto y las barras amarillas son los reales. Entonces, para responder a su pregunta de manera más directa, cada barra sería la suma de diferentes medidas (por ejemplo, real, presupuesto). Espero que tenga sentido.
"use strict";
import { dataViewObjectsParser } from "powerbi-visuals-utils-dataviewutils";
import DataViewObjectsParser = dataViewObjectsParser.DataViewObjectsParser;
import powerbi from "powerbi-visuals-api";
import Fill = powerbi.Fill;
export class VisualSettings extends DataViewObjectsParser {
// public dataPoint: dataPointSettings = new dataPointSettings();
public barchartProperties: BarchartProperties = new BarchartProperties();
}
export class BarchartProperties {
sortBySize: boolean = true;
xAxisFontSize: number = 10;
yAxisFontSize: number = 10;
barColor: Fill = { "solid": { "color": "#018a80" } }; // default color is teal
}
dm-p
En respuesta a cogsie
Hola @cogsie.,
Muchas gracias, ahora puedo compilar el código.
¿Parece que estás intentando algo como un gráfico de viñetas?
Si es así, sugeriría crear roles de datos específicos para cada medida, ya que cada uno tiene un propósito previsto. Esto hará que su código sea más fácil de administrar en lugar de usar un solo rol de datos con múltiples medidas y más intuitivo para el usuario final. El uso de la misma función de datos con varias medidas generará un desafío, ya que todas tendrán el mismo tipo de función, procesando la vista de datos y asignando la las medidas a la barra correspondiente dependerán del orden en que las agregue el usuario final.
Procederé sobre la base de que queremos dos roles de medida distintos; por el bien de la ilustración, los llamaré real y presupuesto y proporcionarle los cambios necesarios para que funcione. ¿Te parece un buen enfoque? Si desea seguir con el enfoque existente, hágamelo saber y le proporcionaré una solución según sus requisitos.
Gracias,
Daniel
cogsie
En respuesta a dm-p
Esto suena como un gran enfoque. Y tienes razón, es similar al gráfico de viñetas, pero mucho más simple.
dm-p
En respuesta a cogsie
¡Estupendo!
He agregado todos los archivos a esta esencia para que descargues el código completo, ya que me gustaría centrarme en explicar los cambios que hice para que puedas desarrollarlos en consecuencia.
capacidades.json – roles de datos
he modificado el roles de datos a lo siguiente:
"dataRoles": [
{
"displayName": "Bar Grouping",
"name": "myCategory",
"kind": "Grouping"
},
{
"displayName": "Actual",
"name": "actual",
"kind": "Measure"
},
{
"displayName": "Budget",
"name": "budget",
"kind": "Measure"
}
]
Esto modificará los roles de datos del objeto visual de la siguiente manera:
Nota que su visual de desarrollo probablemente todavía tendrá el ahora eliminado miMedida allí, por lo que su mejor apuesta es asegurarse de que todo se elimine por completo y se vuelva a aplicar cuando trabaje con el nuevo código que he proporcionado.
capacidades.json – dataViewMappings
El dataViewMappings ahora mira de la siguiente manera:
"dataViewMappings": [
{
"conditions": [
{
"myCategory": {
"max": 1
},
"actual": {
"max": 1
},
"budget": {
"max": 1
}
}
],
"categorical": {
"categories": {
"for": {
"in": "myCategory"
},
"dataReductionAlgorithm": {
"top": {}
}
},
"values": {
"select": [
{
"bind": {
"to": "actual"
}
},
{
"bind": {
"to": "budget"
}
}
]
}
}
}
]
Esto se agrupará por miCategoría y luego enlazar ambas medidas a cada uno. Para mis datos de prueba, la tabla visual tiene el siguiente aspecto:
Tenga en cuenta que este es un conjunto de datos público y estoy tratando Propina como real y Total Facturar como presupuesto.
Echemos un vistazo a la dataViewMapping en la visualización del desarrollador para los datos que he agregado:
La matriz de valores ahora tiene dos entradas: una para real y uno para presupuesto. Podemos ver esto si ampliamos el fuente objeto para cada uno y luego roles – y así es como puede determinar qué entrada está desempeñando qué rol. Los valores de medida para cada uno están entonces en el asociado valores matriz (que coincidirá con el orden de miCategoría). Por brevedad, solo he ampliado esto para real.
visual.ts – BarchartDataPoint interfaz
Necesitamos cambiar la especificación de la interfaz para asegurarnos de que la ‘forma’ de cada punto de datos sea correcta. Esto ahora se ve de la siguiente manera:
export interface BarchartDataPoint{
category: string;
actual: number;
budget: number;
}
(no es esencial, pero es una buena práctica usar camelCase para las propiedades, por lo que he modificado todo para adaptarlo)
Esto ahora significa que nuestro modelo de vista espera dos valores numéricos para cada categoría, lo que coincide con nuestro dataViewMappings, por lo que debemos mapearlos en el modelo de vista a continuación.
visual.ts – crearViewModel función
Si está realizando los cambios anteriores de forma incremental, ahora verá errores en su código, porque el BarchartDataPoint la ‘forma’ de la interfaz no coincide con el objeto que está asignando en el crearViewModel función. Necesitamos actualizar esto.
Con lo anterior en mente, y cómo el dataViewMapping mira, necesitamos cambiar la forma en que iteramos sobre él.
- Anteriormente estaba iterando sobre los valores de medida; He modificado esto para iterar sobre los valores de categoría solo para hacer que el siguiente paso sea más fácil.
- Voy a hacer la solución más simple posible para las medidas usando descriptores de acceso de matriz de [0] por real y [1] por presupuesto.
- Para hacer que su código sea súper resistente, debería filtrar la matriz de valores por papel para garantizar que el valor se proporcione definitivamente, pero podemos preocuparnos por eso mucho más tarde 😉
La parte del código de función para mapear el modelo de vista ahora tiene el siguiente aspecto:
categoryNames.map((c, ci) => { /** c= category, ci = category array index */
BarchartDataPoints.push({
category: <string>c,
actual: <number>categoricalDataView.values[0].values[ci],
budget: <number>categoricalDataView.values[1].values[ci]
});
});
Tiene una clasificación más abajo, por lo que se modificó para usar el real valor:
if(SortBySize){
BarchartDataPoints.sort((x,y) =>{return y.actual - x.actual})
}
visual.ts – actualizar función
Ahora es un caso o vincular todo, desde el modelo de vista correctamente a la lógica de su gráfico.
La primera solución es tu maxValueY asignación: solo conseguimos que coincida con el valor más alto ahora que tiene dos medidas:
let maxValueY: number = d3.max(
viewModel.DataPoints,
(dataPoint:BarchartDataPoint) =>
/** Get the higher of either measure per group */
+Math.max(dataPoint.actual, dataPoint.budget)
);
¡Ahora, la forma real! barraSelecciónFusionada se actualiza para usar ahora el real propiedad del punto de datos:
barSelectionMerged
.attr("x", (dataPoint: BarchartDataPoint) => xScale(dataPoint.category))
.attr("y", (dataPoint: BarchartDataPoint) => yScale(Number(dataPoint.actual)))
.attr("width", xScale.bandwidth())
.attr("height", (dataPoint: BarchartDataPoint) => (plotArea.height - yScale(Number(dataPoint.actual))))
.style("fill",(dataPoint:BarchartDataPoint) => viewModel.BarColor);
… y barSelectionMerged2 obtiene el presupuesto propiedad como parte de su yEscala:
barSelectionMerged2
.attr("x", (dataPoint: BarchartDataPoint) => xScale(dataPoint.category) + xScale.bandwidth() / 4)
.attr("y", (dataPoint: BarchartDataPoint) => yScale(Number(dataPoint.budget)))
.attr("width", xScale.bandwidth() / 2)
.attr("height", (dataPoint: BarchartDataPoint) => (plotArea.height - yScale(Number(dataPoint.budget))))
.style("fill", (dataPoint: BarchartDataPoint) => 'yellow')
.style("fill-opacity", (dataPoint: BarchartDataPoint) => 1);
Verificando
¡Ahora podemos probar en el desarrollador visual! Así es como se ven mis datos arriba:
Y con suerte, aquí es donde debes estar. 🙂
¡Buena suerte!
Daniel
Si mi publicación resuelve su desafío, considere aceptarla como una solución para ayudar a otros miembros del foro a encontrar la respuesta más rápidamente. 🙂
cogsie
En respuesta a dm-p
Vaya, esto es increíble. Esto es exactamente lo que necesito. Y gracias por tu detallada explicación. He aprendido muchísimo de todo esto. Gracias.
dm-p
En respuesta a cogsie
¡Eres muy bienvenido! Me alegro de que lo hayas encontrado útil. 🙂